我们在使用UObject系统的时候,会经常使用Cast函数,能够安全地进行父子转换,能够做到的这一点的原理是什么呢?让我们一探究竟。
Cast函数长这样。
// Dynamically cast an object type-safely.
template <typename To, typename From>
FORCEINLINE To* Cast(From* Src)
{
return TCastImpl<From, To>::DoCast(Src);
}
TCastImpl实际长这样(因为我们是UObject互相转换,所以使用的是下面的特化模板Struct)
template <typename From, typename To>
struct TCastImpl<From, To, ECastType::UObjectToUObject>
{
FORCEINLINE static To* DoCast( UObject* Src )
{
return Src && Src->IsA<To>() ? (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文件生成过程模拟_桃溪小小生的博客-优快云博客 )
恰巧,在这个宏里面声明了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>(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<FClassRegistrationInfo>;
using FEnumDeferredRegistry = TDeferredRegistry<FEnumRegistrationInfo>;
using FStructDeferredRegistry = TDeferredRegistry<FStructRegistrationInfo>;
using FPackageDeferredRegistry = TDeferredRegistry<FPackageRegistrationInfo>;
可以看出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<FRegistrant>& GetRegistrations()
{
return Registrations;
}
并且有一个地方使用这个接口干了可疑的事情
/** Register all loaded classes */
void UClassRegisterAllCompiledInClasses()
{
#if WITH_RELOAD
TArray<UClass*> 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调用成功。