类反射实现分析

UHT 为类产生的反射信息

当在 UE 中新建一个类并继承自 UObject 时,可以在类声明的上一行添加 UCLASS 标记,当执行编译的时候 UBT 会调用 UHT 来根据标记来生成 C++ 代码(不过非 UCLASS 的类也可以用宏来生成反射信息)。

UHT 为类生成的代码为:

  1. 为所有的 UFUNCTION 的函数创建 FName,命名规则为NAME_CLASSNAME_FUNCTIONNAME,如NAME_AMyActor_TestFunc
  2. BlueprintNativeEvent 和 BlueprintImplementEvent 创建同名函数实现,并通过 ProcessEvent 转发调用
  3. 为所有加了 UFUNCTION 的函数生成 Thunk 函数,为当前类的 static 函数,原型为static void execFUNCNAME(UObject* Context, FFrame& Stack, RESULT_DECL)
  4. 创建当前类的 StaticRegisterNatives* 函数,并把上一步提到的 exec 这样的 thunk 函数通过 Name-execFunc 指针 的形式通过 FNativeFunctionRegistrar::RegisterFunctions 注册到 UClass;
  5. 创建出 Z_Construct_UClass_CLASSNAME_NoRegister 函数,返回值是CLASSNAME::StaticClass()
  6. 创建出 Z_Construct_UClass_CLASSNAME_Statics 类(GENERATED_BODY 等宏会把该类添加为我们创建类的友元,使其可以访问私有成员,用于获取成员指针)

Z_Construct_UClass_CLASSNAME_Statics类的结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// AMyActor.h
UCLASS(BlueprintType)
class XXXX_API AMyActor:public AActor
{
GENERATED_BODY()
// ...

UPROPERTY()
int32 ival;
UFUNCTION()
int32 GetIval();
UFUNCTION()
void TESTFUNC();
};

// generated code in AMyActor.gen.cpp
struct Z_Construct_UClass_AMyActor_Statics
{
static UObject* (*const DependentSingletons[])();
static const FClassFunctionLinkInfo FuncInfo[];

#if WITH_METADATA
static const UE4CodeGen_Private::FMetaDataPairParam NewProp_ival_MetaData[];
#endif
static const UE4CodeGen_Private::FIntPropertyParams NewProp_ival;
static const UE4CodeGen_Private::FPropertyParamsBase* const PropPointers[];
static const UE4CodeGen_Private::FImplementedInterfaceParams InterfaceParams[];
static const FCppClassTypeInfoStatic StaticCppClassTypeInfo;
static const UE4CodeGen_Private::FClassParams ClassParams;
};
UObject* (*const Z_Construct_UClass_AMyActor_Statics::DependentSingletons[])() = {
(UObject* (*)())Z_Construct_UClass_AActor,
(UObject* (*)())Z_Construct_UPackage__Script_MicroEnd_423,
};
const FClassFunctionLinkInfo Z_Construct_UClass_AMyActor_Statics::FuncInfo[] = {
{ &Z_Construct_UFunction_AMyActor_GetIval, "GetIval" }, // 3480851337
{ &Z_Construct_UFunction_AMyActor_TESTFUNC, "TESTFUNC" }, // 2984899165
};

该类中的成员为:

  • static UObject* (*const DependentSingletons[])();记录当前类基类的 Z_Construct_UClass_BASECLASSNAME 函数指针,用它可以构造出基类的 UClass,还记录了当前类属于哪个 Package 的函数指针Z_Construct_UPackage__Script_MODULENAME

Z_Construct_UPackage__Script_MODULENAME函数是定义在 MODULE_NAME.init.gen.cpp 里。

  • static const UE4CodeGen_Private::FMetaDataPairParam Class_MetaDataParams[];用于记录 UCLASS 的元数据,如 BlueprintType 标记
  • 反射属性的 F*PropertyParams 以及其 Metadata,均为 static 成员
  • static const UE4CodeGen_Private::FPropertyParamsBase* const PropPointers[];,数组,用于存储当前类所有的反射属性的信息(是个指针数组,用于存储 5.3 中的 static 成员的地址)
  • static const UE4CodeGen_Private::FImplementedInterfaceParams InterfaceParams[];,数组,用于存储当前类所有的接口信息
1
2
3
const UE4CodeGen_Private::FImplementedInterfaceParams Z_Construct_UClass_AMyActor_Statics::InterfaceParams[] = {
{ Z_Construct_UClass_UUnLuaInterface_NoRegister, (int32)VTABLE_OFFSET(AMyActor, IUnLuaInterface), false },
};
  • static const FCppClassTypeInfoStatic StaticCppClassTypeInfo;用于类型萃取,记录当前类是否是抽象类。
1
2
3
const FCppClassTypeInfoStatic Z_Construct_UClass_AMyActor_Statics::StaticCppClassTypeInfo = {
TCppClassTypeTraits<AMyActor>::IsAbstract,
};
  • static const UE4CodeGen_Private::FClassParams ClassParams;构造出 UClass 需要的所有反射数据,统一记录上面所有生成的反射信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const UE4CodeGen_Private::FClassParams Z_Construct_UClass_AMyActor_Statics::ClassParams = {
&AMyActor::StaticClass,
nullptr,
&StaticCppClassTypeInfo,
DependentSingletons,
FuncInfo,
Z_Construct_UClass_AMyActor_Statics::PropPointers,
InterfaceParams,
ARRAY_COUNT(DependentSingletons),
ARRAY_COUNT(FuncInfo),
ARRAY_COUNT(Z_Construct_UClass_AMyActor_Statics::PropPointers),
ARRAY_COUNT(InterfaceParams),
0x009000A0u,
METADATA_PARAMS(Z_Construct_UClass_AMyActor_Statics::Class_MetaDataParams, ARRAY_COUNT(Z_Construct_UClass_AMyActor_Statics::Class_MetaDataParams))
};
  1. 全局函数 Z_Construct_UClass_AMyActor 通过 ClassParams 构造出真正的 UClass 对象。
  2. 使用 IMPLEMENT_CLASS 注册当前类到 GetDeferredClassRegistration(),如果在WITH_HOT_RELOAD 为 true 的情况下也会注册到 GetDeferRegisterClassMap() 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Register a class at startup time.
#define IMPLEMENT_CLASS(TClass, TClassCrc) \
static TClassCompiledInDefer<TClass> AutoInitialize##TClass(TEXT(#TClass), sizeof(TClass), TClassCrc); \
UClass* TClass::GetPrivateStaticClass() \
{ \
static UClass* PrivateStaticClass = NULL; \
if (!PrivateStaticClass) \
{ \
/* this could be handled with templates, but we want it external to avoid code bloat */ \
GetPrivateStaticClassBody(\
StaticPackage(), \
(TCHAR*)TEXT(#TClass) + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0), \
PrivateStaticClass, \
StaticRegisterNatives##TClass, \
sizeof(TClass), \
alignof(TClass), \
(EClassFlags)TClass::StaticClassFlags, \
TClass::StaticClassCastFlags(), \
TClass::StaticConfigName(), \
(UClass::ClassConstructorType)InternalConstructor<TClass>, \
(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>, \
&TClass::AddReferencedObjects, \
&TClass::Super::StaticClass, \
&TClass::WithinClass::StaticClass \
); \
} \
return PrivateStaticClass; \
}

AMyActorIMPLEMENT_CLASS(AMyActor,3240835608)经过预处理之后为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static TClassCompiledInDefer<AMyActor> AutoInitializeAMyActor(TEXT("AMyActor"), sizeof(AMyActor), 3240835608);
UClass * AMyActor::GetPrivateStaticClass() {
static UClass * PrivateStaticClass = NULL;
if (!PrivateStaticClass)
{
GetPrivateStaticClassBody(
StaticPackage(),
(TCHAR*)TEXT("AMyActor") + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0),
PrivateStaticClass,
StaticRegisterNativesAMyActor,
sizeof(AMyActor),
alignof(AMyActor),
(EClassFlags)AMyActor::StaticClassFlags,
AMyActor::StaticClassCastFlags(),
AMyActor::StaticConfigName(),
(UClass::ClassConstructorType)InternalConstructor<AMyActor>,
(UClass::ClassVTableHelperCtorCallerType) InternalVTableHelperCtorCaller<AMyActor>,
&AMyActor::AddReferencedObjects,
&AMyActor::Super::StaticClass,
&AMyActor::WithinClass::StaticClass
);
}
return PrivateStaticClass;
};

其中 TClassCompiledInDefer<TClass> 这个模板类的构造函数中通过调用 UClassCompiledInDefer 将当前反射类的注册到 GetDeferredClassRegistration(),它得到的是一个类型为FFieldCompiledInInfo* 的数组,用于记录引擎中所有反射类的信息,用于在 CoreUObjectModule 启动时将 UHT 生成的这些反射信息在 ProcessNewlyLoadedUObjects 函数中通过 UClassRegisterAllCompiledInClasses 将所有反射类的 UClass 构造出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/** Register all loaded classes */
void UClassRegisterAllCompiledInClasses()
{
#if WITH_HOT_RELOAD
TArray<UClass*> AddedClasses;
#endif
SCOPED_BOOT_TIMING("UClassRegisterAllCompiledInClasses");

TArray<FFieldCompiledInInfo*>& DeferredClassRegistration = GetDeferredClassRegistration();
for (const FFieldCompiledInInfo* Class : DeferredClassRegistration)
{
UClass* RegisteredClass = Class->Register();
#if WITH_HOT_RELOAD
if (GIsHotReload && Class->OldClass == nullptr)
{
AddedClasses.Add(RegisteredClass);
}
#endif
}
DeferredClassRegistration.Empty();

#if WITH_HOT_RELOAD
if (AddedClasses.Num() > 0)
{
FCoreUObjectDelegates::RegisterHotReloadAddedClassesDelegate.Broadcast(AddedClasses);
}
#endif
}

TClassCompiledInDefer<AMyActor>Register函数就是调用 AMyActor::StaticClass 的,然后 StaticClass 中调用 GetPrivateStaticClass,其中有一个 static 对象,就是当前类的 UClass,所以它只会构造依次,使用UXXXX::StaticClass 都是直接获得。

注意:UClass 的构造是跟着模块的启动创建的,所以之后当引擎启动到一个模块的时候它的 UClass 才被创建出来)。

非 UCLASS 的反射

有些继承自 UObject 的类是没有加 UCLASS 标记的,所以也不会包含 gen.cppgenerated.h文件,但是 UE 也提供了非 UCLASS 的反射方法,类似于 UTextureBuffer 这个类。

在类内时添加 DECLARE_CASTED_CLASS_INTRINSIC_WITH_API 宏用于手动添加,实现类似 UHT 生成 GENERATED_BODY 宏的操作:

1
2
3
4
5
6
class UTextBuffer
: public UObject
, public FOutputDevice
{
DECLARE_CASTED_CLASS_INTRINSIC_WITH_API(UTextBuffer, UObject, 0, TEXT("/Script/CoreUObject"), CASTCLASS_None, COREUOBJECT_API)
}

DECLARE_CASTED_CLASS_INTRINSIC_WITH_API可以处理类似 generated.h 的行为,但是 gen.cpp 里创建出 static TClassCompiledInDefer<CLASS_NAME> 的代码还没有,UE 提供了另一个宏:

1
IMPLEMENT_CORE_INTRINSIC_CLASS(UTextBuffer, UObject, { });

虽然和 gen.cpp 里通过 UHT 生成的代码不同,但是统一使用 TClassCompiledInDefer<TClass>FCompiledInDefer来注册到引擎中。
这样就实现了可以不使用 UCLASS 标记也可以为继承自 UObject 的类生成反射信息。

UClass 的构造思路

前面讲了这么多都是在分析 UE 创建 UClass 的代码,我想从 UE 的实现思路上分析一下设计过程。

  1. 首先 UHT 通过分析代码创建出 gen.cpp 和 generated.h 中间记录着当前类的反射信息、类本身的反射信息、类中函数的反射信息、类数据成员的反射信息。
  2. 当前类的反射信息(类、成员函数、数据成员)等被统一存储在一个名为 Z_Construct_UClass_CLASSNAME_Statics 的结构中;
  3. 该结构通过 IMPLEMENT_CLASS 生成的代码将当前类添加到 GetDeferredClassRegistration() 中。因为 全局作用域 static 对象的构造时机 是先于主函数的第一条语句的,所以当进入引擎逻辑的时候,引擎内置的模块中类的 TClassCompiledInDefer<> 都已经被创建完毕,在编辑器模式下, 因为不同的模块都是编译为 DLL 的,所以在加载模块的时候它们的 static 对象才会被创建。
1
2
// gen.cpp
static TClassCompiledInDefer<AMyActor> AutoInitializeAMyActor(TEXT("AMyActor"), sizeof(AMyActor), 3240835608);
  1. 与上一步同样的手法,把类生成的反射信息通过 FCompiledInDefer 收集到GetDeferredCompiledInRegistration()
1
static FCompiledInDefer Z_CompiledInDefer_UClass_AMyActor(Z_Construct_UClass_AMyActor, &AMyActor::StaticClass, TEXT("/Script/MicroEnd_423"), TEXT("AMyActor"), false, nullptr, nullptr, nullptr);

引擎如何使用生成的反射信息

在 UHT 生成反射的代码之后,引擎会根据这些代码生成 UClass、UStruct、UEnum、UFunction 和 UProperty 等。

它们都是在 ProcessNewlyLoadedUObjects 中被执行的,注意 该函数会进来很多次,当每一个模块被加载的时候都会走一遍,因为在 Obj.cppInitUObject函数中,把函数 ProcessNewlyLoadedUObjects 添加到了 FModuleManager::Get().OnProcessLoadedObjectsCallback() 中:

1
2
3
#if !USE_PER_MODULE_UOBJECT_BOOTSTRAP // otherwise this is already done
FModuleManager::Get().OnProcessLoadedObjectsCallback().AddStatic(ProcessNewlyLoadedUObjects);
#endif

之所以 要这么做,是因为 UE 的 Module 中都会有很多的反射类,但引擎一启动并不是所有的类在同一时刻都被加载了,因为模块有不同的加载时机,所以引擎中对于 UClass 的构造也不是一个一次性过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// CoreUObject/Private/UObject/UObjectBase.cpp
void ProcessNewlyLoadedUObjects()
{
LLM_SCOPE(ELLMTag::UObject);
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("ProcessNewlyLoadedUObjects"), STAT_ProcessNewlyLoadedUObjects, STATGROUP_ObjectVerbose);

UClassRegisterAllCompiledInClasses();

const TArray<UClass* (*)()>& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration();
const TArray<FPendingStructRegistrant>& DeferredCompiledInStructRegistration = GetDeferredCompiledInStructRegistration();
const TArray<FPendingEnumRegistrant>& DeferredCompiledInEnumRegistration = GetDeferredCompiledInEnumRegistration();

bool bNewUObjects = false;
while(GFirstPendingRegistrant || DeferredCompiledInRegistration.Num() || DeferredCompiledInStructRegistration.Num() || DeferredCompiledInEnumRegistration.Num())
{
bNewUObjects = true;
UObjectProcessRegistrants();
UObjectLoadAllCompiledInStructs();
UObjectLoadAllCompiledInDefaultProperties();
}
#if WITH_HOT_RELOAD
UClassReplaceHotReloadClasses();
#endif

if (bNewUObjects && !GIsInitialLoad)
{
UClass::AssembleReferenceTokenStreams();
}
}

UClass 构造的调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// CoreUObject/Private/UObject/UObjectBase.cpp
/** Register all loaded classes */
void UClassRegisterAllCompiledInClasses()
{
#if WITH_HOT_RELOAD
TArray<UClass*> AddedClasses;
#endif

TArray<FFieldCompiledInInfo*>& DeferredClassRegistration = GetDeferredClassRegistration();
for (const FFieldCompiledInInfo* Class : DeferredClassRegistration)
{
UClass* RegisteredClass = Class->Register();
#if WITH_HOT_RELOAD
if (GIsHotReload && Class->OldClass == nullptr)
{
AddedClasses.Add(RegisteredClass);
}
#endif
}
DeferredClassRegistration.Empty();

#if WITH_HOT_RELOAD
if (AddedClasses.Num() > 0)
{
FCoreUObjectDelegates::RegisterHotReloadAddedClassesDelegate.Broadcast(AddedClasses);
}
#endif
}

可以看到,在 UClassRegisterAllCompiledInClasses 只是去调用了每个反射类的 StaticClass 函数(Class->Register()内部是对类型的 StaticClass 的转发调用),在开启 WITH_HOT_RELOAD 的情况下也会把新的 UClass 给代理调用传递出去,然后把当前的数组置空。

之所以要置空,就是因为前面说的,UE 的 UClass 构造是一个模块一个模块来执行的,当一个模块执行完毕之后就把当前模块注册到 GetDeferredClassRegistration() 里的元素置空,等着下个模块启动的时候(加载 DLL 时它们的 static 成员会构造然后注册到里面),再执行 LoadModuleWithFailureReason 就是又一遍循环。

在模块启动的时候会执行 LoadModuleWithFailureReason 里面调用了这个 Delegate,所以每一个模块启动的时候都会执行ProcessNewlyLoadedUObjects,把自己当前模块中的 UClass/UStruct/UEnum 都构造出来。