我们在使用UObject系统的时候,会经常使用Cast函数,能够安全地进行父子转换,能够做到的这一点的原理是什么呢?让我们一探究竟。
Cast函数长这样。
- // Dynamically cast an object type-safely.
- template <typename To, typename From>
- FORCEINLINE To* Cast(From* Src)
- {
- return TCastImpl
::DoCast(Src); - }
TCastImpl实际长这样(因为我们是UObject互相转换,所以使用的是下面的特化模板Struct)
- template <typename From, typename To>
- struct TCastImpl
- {
- FORCEINLINE static To* DoCast( UObject* Src )
- {
- return Src && Src->IsA
() ? (To*)Src : nullptr; - }
- ....
- }
可见,内部是使用了UObject::IsA实现的判断,成功则使用C Cast转换。
我们稍微多探寻几步,会发现其实是利用了 UStruct::IsChildOf接口。
- /** Struct this inherits from, may be null */
- UStruct* GetSuperStruct() const
- {
- return SuperStruct;
- }
- bool UStruct::IsChildOf( const UStruct* SomeBase ) const
- {
- if (SomeBase == nullptr)
- {
- return false;
- }
-
- bool bOldResult = false;
- for ( const UStruct* TempStruct=this; TempStruct; TempStruct=TempStruct->GetSuperStruct() )
- {
- if ( TempStruct == SomeBase )
- {
- bOldResult = true;
- break;
- }
- }
- ......
- return bOldResult;
- }
- #endif
所以判断UStruct A是否UStruct B的Child,只要依次上溯SuperStruct对比即可。
所以问题变为:UStruct的SuperStruct是什么时候赋值的?
通过对代码的研究观察,我们发现有这么个函数
- void UClass::SetSuperStruct(UStruct* NewSuperStruct)
- {
- UnhashObject(this);
- ClearFunctionMapsCaches();
- Super::SetSuperStruct(NewSuperStruct);
-
- if (!GetSparseClassDataStruct())
- {
- if (UScriptStruct* SparseClassDataStructArchetype = GetSparseClassDataArchetypeStruct())
- {
- SetSparseClassDataStruct(SparseClassDataStructArchetype);
- }
- }
-
- HashObject(this);
- }
对于UClass来说,手动调用了SetSuperStruct函数进行了赋值。
再追寻UClass::SetSuperStruct的调用过程,我们发现这么一条路线
GetPrivateStaticClass->GetPrivateStaticClassBody->InitializePrivateStaticClass->SetSuperStruct
.generated.h文件有DECLARE_CLASS宏
DECLARE_CLASS(AMyActor, AActor, COMPILED_IN_FLAGS(0 | CLASS_Config), CASTCLASS_None, TEXT("/Script/MyGame"), NO_API) \
( 关于GENERATED_BODY宏如何展开,可以参考我的另一篇文章UE4 generated.h文件生成过程模拟_桃溪小小生的博客-CSDN博客 )
恰巧,在这个宏里面声明了GetPrivateStaticClass函数

可以看见同时还声明并实现了StatciClass,内部调用的GetPrivateStaticClass())
.gen.cpp中有如下代码
IMPLEMENT_CLASS_NO_AUTO_REGISTRATION(AMyActor);
GetPrivateStaticClass函数恰好在IMPLEMENT_CLASS宏里面进行了实现。

OK,现在入口函数是GetPrivateStaticClass,只要调用了这个函数,最终就会向文章开头那样建立起Class的SuperStruct。可以看到,类的StaticClass中调用了GetPrivateStaticClass函数。
到这里,我们从SetSuperStruct追寻到了StaticClass函数。接下来,问题演变为:在哪里调用了类的StaticClass函数?
根据UE的设计规则,一定不是在某个地方显式调用了T::StaticClass(为什么?因为引擎模块并不知道具体应用的某个类的存在,当然不可能显式调用),而是作为某种函数回调传给引擎模块,让引擎模块在某个阶段进行回调。所以我们来搜寻一下,哪里处理了StaticClass函数。
我们在.gen.cpp找到了下面这个地方
- struct Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_Statics
- {
- static const FClassRegisterCompiledInInfo ClassInfo[];
- };
- const FClassRegisterCompiledInInfo Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_Statics::ClassInfo[] = {
- { Z_Construct_UClass_AMyActor, AMyActor::StaticClass, TEXT("AMyActor"), &Z_Registration_Info_UClass_AMyActor, CONSTRUCT_RELOAD_VERSION_INFO(FClassReloadVersionInfo, sizeof(AMyActor), 3805165363U) },
- };
- static FRegisterCompiledInInfo Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_4051704720(TEXT("/Script/MyGame"),
- Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_Statics::ClassInfo, UE_ARRAY_COUNT(Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_Statics::ClassInfo),
- nullptr, 0,
- nullptr, 0);
.gen.cpp中有一个FRegisterCompiledInInfo类型的static变量,static意味着什么?意味着在程序正式启动前,变量就执行了初始化,而其中恰好用到了StaticClass函数。
我们看到ClassInfo里面用到了StaticClass,ClassInfo是个FClassRegisterCompiledInInfo,长这样
-
- /**
- * Composite class register compiled in info
- */
- struct FClassRegisterCompiledInInfo
- {
- class UClass* (*OuterRegister)();
- class UClass* (*InnerRegister)();
- const TCHAR* Name;
- FClassRegistrationInfo* Info;
- FClassReloadVersionInfo VersionInfo;
- };
ClassInfo初始化的时候,StaticClass作为第二个成员变量,这里对应的是InnerRegister,它也的确是一个函数指针。
Unreal如此使用static变量,就是为了要在程序正式启动前,收集到所有的class的信息,然后在Unreal启动时进行反射处理。
所以,下一个问题,收集了StaticClass后,什么时候调用它?
我们进入FRegisterCompiledInInfo里面看一下,它有哪些内容。
- struct FRegisterCompiledInInfo
- {
- template <typename ... Args>
- FRegisterCompiledInInfo(Args&& ... args)
- {
- RegisterCompiledInInfo(std::forward
(args)...); - }
- };
它进行了参数的转发,然后真正执行的是RegisterCompiledInInfo,我们来看看RegisterCompiledInInfo
- // Multiple registrations
- void RegisterCompiledInInfo(const TCHAR* PackageName, const FClassRegisterCompiledInInfo* ClassInfo, size_t NumClassInfo, const FStructRegisterCompiledInInfo* StructInfo, size_t NumStructInfo, const FEnumRegisterCompiledInInfo* EnumInfo, size_t NumEnumInfo)
- {
- for (size_t Index = 0; Index < NumClassInfo; ++Index)
- {
- const FClassRegisterCompiledInInfo& Info = ClassInfo[Index];
- RegisterCompiledInInfo(Info.OuterRegister, Info.InnerRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);
- }
-
- for (size_t Index = 0; Index < NumStructInfo; ++Index)
- {
- const FStructRegisterCompiledInInfo& Info = StructInfo[Index];
- RegisterCompiledInInfo(Info.OuterRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);
- if (Info.CreateCppStructOps != nullptr)
- {
- UScriptStruct::DeferCppStructOps(FName(Info.Name), (UScriptStruct::ICppStructOps*)Info.CreateCppStructOps());
- }
- }
-
- for (size_t Index = 0; Index < NumEnumInfo; ++Index)
- {
- const FEnumRegisterCompiledInInfo& Info = EnumInfo[Index];
- RegisterCompiledInInfo(Info.OuterRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);
- }
- }
RegisterCompiledInInfo有好几个重载,为什么我专门列出这个实现呢?因为static变量的参数对应的就是这个版本,第一个参数是TEXT("/Script/MyGame"),这里匹配的就是这个版本。
对于InnerRegister的使用(记住,它现在就是我们的StaticClass),主要是下面这行代码
RegisterCompiledInInfo(Info.OuterRegister, Info.InnerRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);
它长下面这样
- void RegisterCompiledInInfo(class UClass* (*InOuterRegister)(), class UClass* (*InInnerRegister)(), const TCHAR* InPackageName, const TCHAR* InName, FClassRegistrationInfo& InInfo, const FClassReloadVersionInfo& InVersionInfo)
- {
- check(InOuterRegister);
- check(InInnerRegister);
- bool bExisting = FClassDeferredRegistry::Get().AddRegistration(InOuterRegister, InInnerRegister, InPackageName, InName, InInfo, InVersionInfo, nullptr);
- #if WITH_RELOAD
- if (bExisting && !IsReloadActive())
- {
- // Class exists, this can only happen during hot-reload or live coding
- UE_LOG(LogUObjectBase, Fatal, TEXT("Trying to recreate class '%s' outside of hot reload and live coding!"), InName);
- }
- #endif
- FString NoPrefix(UObjectBase::RemoveClassPrefix(InName));
- NotifyRegistrationEvent(InPackageName, *NoPrefix, ENotifyRegistrationType::NRT_Class, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InOuterRegister), false);
- NotifyRegistrationEvent(InPackageName, *(FString(DEFAULT_OBJECT_PREFIX) + NoPrefix), ENotifyRegistrationType::NRT_ClassCDO, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InOuterRegister), false);
- }
使用InInnerRegister的代码是下面这样
bool bExisting = FClassDeferredRegistry::Get().AddRegistration(InOuterRegister, InInnerRegister, InPackageName, InName, InInfo, InVersionInfo, nullptr);
又被添加到了别的地方,我们继续看AddRegistration的实现
- bool TDeferredRegistry::AddRegistration(TType* (*InOuterRegister)(), TType* (*InInnerRegister)(), const TCHAR* InPackageName, const TCHAR* InName, TInfo& InInfo, const TVersion& InVersion, FFieldCompiledInInfo* DeprecatedFieldInfo)
- {
- #if WITH_RELOAD
- const FPackageAndNameKey Key = FPackageAndNameKey{ InPackageName, InName };
- TInfo** ExistingInfo = InfoMap.Find(Key);
-
- bool bHasChanged = !ExistingInfo || (*ExistingInfo)->ReloadVersionInfo != InVersion;
- InInfo.ReloadVersionInfo = InVersion;
- TType* OldSingleton = ExistingInfo ? ((*ExistingInfo)->InnerSingleton ? (*ExistingInfo)->InnerSingleton : (*ExistingInfo)->OuterSingleton) : nullptr;
-
- bool bAdd = true;
- if (ExistingInfo)
- {
- if (IReload* Reload = GetActiveReloadInterface())
- {
- bAdd = Reload->GetEnableReinstancing(bHasChanged);
- }
- if (bAdd)
- {
- if (!bHasChanged)
- {
- // With live coding, the existing might be the same as the new info.
- // We still invoke the copy method to allow UClasses to clear the singletons.
- UpdateSingletons(InInfo, **ExistingInfo);
- }
- *ExistingInfo = &InInfo;
- }
- }
- else
- {
- InfoMap.Add(Key, &InInfo);
- }
- if (bAdd)
- {
- Registrations.Add(FRegistrant{ InOuterRegister, InInnerRegister, InPackageName, &InInfo, OldSingleton, DeprecatedFieldInfo, bHasChanged });
- }
- return ExistingInfo != nullptr;
- #else
- Registrations.Add(FRegistrant{ InOuterRegister, InInnerRegister, InPackageName, &InInfo, DeprecatedFieldInfo });
- return false;
- #endif
- }
主要起作用的是下面这行代码
Registrations.Add(FRegistrant{ InOuterRegister, InInnerRegister, InPackageName, &InInfo, DeprecatedFieldInfo });
这里有个小问题,我们之前使用的是FClassDeferredRegistry::Get().AddRegistration
为什么这里是TDeferredRegistry::AddRegistration?
FClassDeferredRegistry 定义如下
- using FClassDeferredRegistry = TDeferredRegistry
; - using FEnumDeferredRegistry = TDeferredRegistry
; - using FStructDeferredRegistry = TDeferredRegistry
; - using FPackageDeferredRegistry = TDeferredRegistry
;
可以看出TDeferredRegistry是个模板类,而FClassDeferredRegistry是模板进行了实例化。因为我们这里处理的是Class,所以使用的是FClassDeferredRegistry,同级别的其他实例化用于处理Enum Struct等。
到这里,StaticClass被构造为FRegistrant,添加进数组Registrations里面去了。
FRegistrant长这样
- /**
- * Maintains information about a pending registration
- */
- struct FRegistrant
- {
- TType* (*OuterRegisterFn)();
- TType* (*InnerRegisterFn)();
- const TCHAR* PackageName;
- TInfo* Info;
- #if WITH_RELOAD
- TType* OldSingleton;
- #endif
- FFieldCompiledInInfo* DeprecatedFieldInfo;
-
- #if WITH_RELOAD
- bool bHasChanged;
- #endif
- };
这里StaticClass变为了InnerRegisterFn。
所以,问题进一步变为,什么时候使用Registrations里面的InnerRegisterFn?
因为Registrations是个成员变量,我们注意到TDeferredRegistry有个接口返回了它
- /**
- * Return the collection of registrations
- */
- TArray
& GetRegistrations() - {
- return Registrations;
- }
并且有一个地方使用这个接口干了可疑的事情
- /** Register all loaded classes */
- void UClassRegisterAllCompiledInClasses()
- {
- #if WITH_RELOAD
- TArray
AddedClasses; - #endif
- SCOPED_BOOT_TIMING("UClassRegisterAllCompiledInClasses");
-
- FClassDeferredRegistry& Registry = FClassDeferredRegistry::Get();
-
- Registry.ProcessChangedObjects();
-
- for (const FClassDeferredRegistry::FRegistrant& Registrant : Registry.GetRegistrations())
- {
- UClass* RegisteredClass = FClassDeferredRegistry::InnerRegister(Registrant);
- #if WITH_RELOAD
- if (IsReloadActive() && Registrant.OldSingleton == nullptr)
- {
- AddedClasses.Add(RegisteredClass);
- }
- #endif
- }
- ......
- }
UClassRegisterAllCompiledInClasses函数里面,最终拿到了InnerRegister,并且进行了调用!
UClassRegisterAllCompiledInClasses顾名思义,就是对所有的Class进行注册!
它的调用地方如下

原来,在CoreUObject模块,启动的时候,一开始就启动了所有的StaticClass的调用,进行建立起子类的SuperStruct信息。
所以针对父子关系的建立,我们作如下总结
UHT工具会为源文件生成反射文件.generated.h和gen.cpp,cpp文件有static变量,用于在程序启动一开始就将类的信息注册到FClassDeferredRegistry里面。
在CoreUObject的启动函数中,拿到所有的注册信息,进行了调用,从而执行StaticClass函数,在这个函数里面,会一步步建立UClass的SuperStruct信息。最后Cast函数调用的时候,使用UClass::IsChildOf接口,它使用了UClass的SuperStruct信息,判断所有父类中是否存在要转的Class,如果存在,则IsChildOf成立,Cast调用成功。