UHT 为类产生的反射信息 当在 UE 中新建一个类并继承自 UObject
时,可以在类声明的上一行添加 UCLASS
标记,当执行编译的时候 UBT 会调用 UHT 来根据标记来生成 C++ 代码(不过非 UCLASS 的类也可以用宏来生成反射信息)。
UHT 为类生成的代码为:
为所有的 UFUNCTION 的函数创建 FName,命名规则为NAME_CLASSNAME_FUNCTIONNAME
,如NAME_AMyActor_TestFunc
BlueprintNativeEvent 和 BlueprintImplementEvent 创建同名函数实现,并通过 ProcessEvent
转发调用
为所有加了 UFUNCTION 的函数生成 Thunk 函数,为当前类的 static 函数,原型为static void execFUNCNAME(UObject* Context, FFrame& Stack, RESULT_DECL)
;
创建当前类的 StaticRegisterNatives*
函数,并把上一步提到的 exec
这样的 thunk 函数通过 Name-execFunc 指针
的形式通过 FNativeFunctionRegistrar::RegisterFunctions
注册到 UClass;
创建出 Z_Construct_UClass_CLASSNAME_NoRegister
函数,返回值是CLASSNAME::StaticClass()
创建出 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 UCLASS (BlueprintType)class XXXX_API AMyActor :public AActor{ GENERATED_BODY () UPROPERTY () int32 ival; UFUNCTION () int32 GetIval () ; UFUNCTION () void TESTFUNC () ; }; 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" }, { &Z_Construct_UFunction_AMyActor_TESTFUNC, "TESTFUNC" }, };
该类中的成员为:
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), 0x009000A0 u, METADATA_PARAMS (Z_Construct_UClass_AMyActor_Statics::Class_MetaDataParams, ARRAY_COUNT (Z_Construct_UClass_AMyActor_Statics::Class_MetaDataParams)) };
全局函数 Z_Construct_UClass_AMyActor
通过 ClassParams
构造出真正的 UClass 对象。
使用 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 #define IMPLEMENT_CLASS(TClass, TClassCrc) \ static TClassCompiledInDefer<TClass> AutoInitialize##TClass(TEXT(#TClass), sizeof(TClass), TClassCrc); \ UClass* TClass::GetPrivateStaticClass() \ { \ static UClass* PrivateStaticClass = NULL; \ if (!PrivateStaticClass) \ { \ \ 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; \ }
如 AMyActor
的IMPLEMENT_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 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.cpp
和generated.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 的实现思路上分析一下设计过程。
首先 UHT 通过分析代码创建出 gen.cpp 和 generated.h 中间记录着当前类的反射信息、类本身的反射信息、类中函数的反射信息、类数据成员的反射信息。
当前类的反射信息(类、成员函数、数据成员)等被统一存储在一个名为 Z_Construct_UClass_CLASSNAME_Statics
的结构中;
该结构通过 IMPLEMENT_CLASS
生成的代码将当前类添加到 GetDeferredClassRegistration() 中。因为 全局作用域 static 对象的构造时机 是先于主函数的第一条语句的,所以当进入引擎逻辑的时候,引擎内置的模块中类的 TClassCompiledInDefer<>
都已经被创建完毕,在编辑器模式下, 因为不同的模块都是编译为 DLL 的,所以在加载模块的时候它们的 static 对象才会被创建。
1 2 static TClassCompiledInDefer<AMyActor> AutoInitializeAMyActor (TEXT("AMyActor" ), sizeof (AMyActor), 3240835608 ) ;
与上一步同样的手法,把类生成的反射信息通过 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.cpp 的InitUObject 函数中,把函数 ProcessNewlyLoadedUObjects 添加到了 FModuleManager::Get().OnProcessLoadedObjectsCallback()
中:
1 2 3 #if !USE_PER_MODULE_UOBJECT_BOOTSTRAP 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 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 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 都构造出来。