• 从Unreal的Cast函数探寻反射系统父子关系的建立过程


    我们在使用UObject系统的时候,会经常使用Cast函数,能够安全地进行父子转换,能够做到的这一点的原理是什么呢?让我们一探究竟。

    Cast函数长这样。

    1. // Dynamically cast an object type-safely.
    2. template <typename To, typename From>
    3. FORCEINLINE To* Cast(From* Src)
    4. {
    5. return TCastImpl::DoCast(Src);
    6. }

    TCastImpl实际长这样(因为我们是UObject互相转换,所以使用的是下面的特化模板Struct)

    1. template <typename From, typename To>
    2. struct TCastImpl
    3. {
    4. FORCEINLINE static To* DoCast( UObject* Src )
    5. {
    6. return Src && Src->IsA() ? (To*)Src : nullptr;
    7. }
    8. ....
    9. }

    可见,内部是使用了UObject::IsA实现的判断,成功则使用C Cast转换。

    我们稍微多探寻几步,会发现其实是利用了 UStruct::IsChildOf接口。

    1. /** Struct this inherits from, may be null */
    2. UStruct* GetSuperStruct() const
    3. {
    4. return SuperStruct;
    5. }
    6. bool UStruct::IsChildOf( const UStruct* SomeBase ) const
    7. {
    8. if (SomeBase == nullptr)
    9. {
    10. return false;
    11. }
    12. bool bOldResult = false;
    13. for ( const UStruct* TempStruct=this; TempStruct; TempStruct=TempStruct->GetSuperStruct() )
    14. {
    15. if ( TempStruct == SomeBase )
    16. {
    17. bOldResult = true;
    18. break;
    19. }
    20. }
    21. ......
    22. return bOldResult;
    23. }
    24. #endif

    所以判断UStruct A是否UStruct B的Child,只要依次上溯SuperStruct对比即可。

    所以问题变为:UStruct的SuperStruct是什么时候赋值的?

    通过对代码的研究观察,我们发现有这么个函数

    1. void UClass::SetSuperStruct(UStruct* NewSuperStruct)
    2. {
    3. UnhashObject(this);
    4. ClearFunctionMapsCaches();
    5. Super::SetSuperStruct(NewSuperStruct);
    6. if (!GetSparseClassDataStruct())
    7. {
    8. if (UScriptStruct* SparseClassDataStructArchetype = GetSparseClassDataArchetypeStruct())
    9. {
    10. SetSparseClassDataStruct(SparseClassDataStructArchetype);
    11. }
    12. }
    13. HashObject(this);
    14. }

    对于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找到了下面这个地方

    1. struct Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_Statics
    2. {
    3. static const FClassRegisterCompiledInInfo ClassInfo[];
    4. };
    5. const FClassRegisterCompiledInInfo Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_Statics::ClassInfo[] = {
    6. { Z_Construct_UClass_AMyActor, AMyActor::StaticClass, TEXT("AMyActor"), &Z_Registration_Info_UClass_AMyActor, CONSTRUCT_RELOAD_VERSION_INFO(FClassReloadVersionInfo, sizeof(AMyActor), 3805165363U) },
    7. };
    8. static FRegisterCompiledInInfo Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_4051704720(TEXT("/Script/MyGame"),
    9. 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),
    10. nullptr, 0,
    11. nullptr, 0);

    .gen.cpp中有一个FRegisterCompiledInInfo类型的static变量,static意味着什么?意味着在程序正式启动前,变量就执行了初始化,而其中恰好用到了StaticClass函数。

    我们看到ClassInfo里面用到了StaticClass,ClassInfo是个FClassRegisterCompiledInInfo,长这样

    1. /**
    2. * Composite class register compiled in info
    3. */
    4. struct FClassRegisterCompiledInInfo
    5. {
    6. class UClass* (*OuterRegister)();
    7. class UClass* (*InnerRegister)();
    8. const TCHAR* Name;
    9. FClassRegistrationInfo* Info;
    10. FClassReloadVersionInfo VersionInfo;
    11. };

    ClassInfo初始化的时候,StaticClass作为第二个成员变量,这里对应的是InnerRegister,它也的确是一个函数指针。

    Unreal如此使用static变量,就是为了要在程序正式启动前,收集到所有的class的信息,然后在Unreal启动时进行反射处理。

    所以,下一个问题,收集了StaticClass后,什么时候调用它?

    我们进入FRegisterCompiledInInfo里面看一下,它有哪些内容。

    1. struct FRegisterCompiledInInfo
    2. {
    3. template <typename ... Args>
    4. FRegisterCompiledInInfo(Args&& ... args)
    5. {
    6. RegisterCompiledInInfo(std::forward(args)...);
    7. }
    8. };

    它进行了参数的转发,然后真正执行的是RegisterCompiledInInfo,我们来看看RegisterCompiledInInfo

    1. // Multiple registrations
    2. void RegisterCompiledInInfo(const TCHAR* PackageName, const FClassRegisterCompiledInInfo* ClassInfo, size_t NumClassInfo, const FStructRegisterCompiledInInfo* StructInfo, size_t NumStructInfo, const FEnumRegisterCompiledInInfo* EnumInfo, size_t NumEnumInfo)
    3. {
    4. for (size_t Index = 0; Index < NumClassInfo; ++Index)
    5. {
    6. const FClassRegisterCompiledInInfo& Info = ClassInfo[Index];
    7. RegisterCompiledInInfo(Info.OuterRegister, Info.InnerRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);
    8. }
    9. for (size_t Index = 0; Index < NumStructInfo; ++Index)
    10. {
    11. const FStructRegisterCompiledInInfo& Info = StructInfo[Index];
    12. RegisterCompiledInInfo(Info.OuterRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);
    13. if (Info.CreateCppStructOps != nullptr)
    14. {
    15. UScriptStruct::DeferCppStructOps(FName(Info.Name), (UScriptStruct::ICppStructOps*)Info.CreateCppStructOps());
    16. }
    17. }
    18. for (size_t Index = 0; Index < NumEnumInfo; ++Index)
    19. {
    20. const FEnumRegisterCompiledInInfo& Info = EnumInfo[Index];
    21. RegisterCompiledInInfo(Info.OuterRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);
    22. }
    23. }

    RegisterCompiledInInfo有好几个重载,为什么我专门列出这个实现呢?因为static变量的参数对应的就是这个版本,第一个参数是TEXT("/Script/MyGame"),这里匹配的就是这个版本。

    对于InnerRegister的使用(记住,它现在就是我们的StaticClass),主要是下面这行代码

    RegisterCompiledInInfo(Info.OuterRegister, Info.InnerRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);

    它长下面这样

    1. void RegisterCompiledInInfo(class UClass* (*InOuterRegister)(), class UClass* (*InInnerRegister)(), const TCHAR* InPackageName, const TCHAR* InName, FClassRegistrationInfo& InInfo, const FClassReloadVersionInfo& InVersionInfo)
    2. {
    3. check(InOuterRegister);
    4. check(InInnerRegister);
    5. bool bExisting = FClassDeferredRegistry::Get().AddRegistration(InOuterRegister, InInnerRegister, InPackageName, InName, InInfo, InVersionInfo, nullptr);
    6. #if WITH_RELOAD
    7. if (bExisting && !IsReloadActive())
    8. {
    9. // Class exists, this can only happen during hot-reload or live coding
    10. UE_LOG(LogUObjectBase, Fatal, TEXT("Trying to recreate class '%s' outside of hot reload and live coding!"), InName);
    11. }
    12. #endif
    13. FString NoPrefix(UObjectBase::RemoveClassPrefix(InName));
    14. NotifyRegistrationEvent(InPackageName, *NoPrefix, ENotifyRegistrationType::NRT_Class, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InOuterRegister), false);
    15. NotifyRegistrationEvent(InPackageName, *(FString(DEFAULT_OBJECT_PREFIX) + NoPrefix), ENotifyRegistrationType::NRT_ClassCDO, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InOuterRegister), false);
    16. }

    使用InInnerRegister的代码是下面这样

    	bool bExisting = FClassDeferredRegistry::Get().AddRegistration(InOuterRegister, InInnerRegister, InPackageName, InName, InInfo, InVersionInfo, nullptr);
    

    又被添加到了别的地方,我们继续看AddRegistration的实现

    1. bool TDeferredRegistry::AddRegistration(TType* (*InOuterRegister)(), TType* (*InInnerRegister)(), const TCHAR* InPackageName, const TCHAR* InName, TInfo& InInfo, const TVersion& InVersion, FFieldCompiledInInfo* DeprecatedFieldInfo)
    2. {
    3. #if WITH_RELOAD
    4. const FPackageAndNameKey Key = FPackageAndNameKey{ InPackageName, InName };
    5. TInfo** ExistingInfo = InfoMap.Find(Key);
    6. bool bHasChanged = !ExistingInfo || (*ExistingInfo)->ReloadVersionInfo != InVersion;
    7. InInfo.ReloadVersionInfo = InVersion;
    8. TType* OldSingleton = ExistingInfo ? ((*ExistingInfo)->InnerSingleton ? (*ExistingInfo)->InnerSingleton : (*ExistingInfo)->OuterSingleton) : nullptr;
    9. bool bAdd = true;
    10. if (ExistingInfo)
    11. {
    12. if (IReload* Reload = GetActiveReloadInterface())
    13. {
    14. bAdd = Reload->GetEnableReinstancing(bHasChanged);
    15. }
    16. if (bAdd)
    17. {
    18. if (!bHasChanged)
    19. {
    20. // With live coding, the existing might be the same as the new info.
    21. // We still invoke the copy method to allow UClasses to clear the singletons.
    22. UpdateSingletons(InInfo, **ExistingInfo);
    23. }
    24. *ExistingInfo = &InInfo;
    25. }
    26. }
    27. else
    28. {
    29. InfoMap.Add(Key, &InInfo);
    30. }
    31. if (bAdd)
    32. {
    33. Registrations.Add(FRegistrant{ InOuterRegister, InInnerRegister, InPackageName, &InInfo, OldSingleton, DeprecatedFieldInfo, bHasChanged });
    34. }
    35. return ExistingInfo != nullptr;
    36. #else
    37. Registrations.Add(FRegistrant{ InOuterRegister, InInnerRegister, InPackageName, &InInfo, DeprecatedFieldInfo });
    38. return false;
    39. #endif
    40. }

    主要起作用的是下面这行代码

    		Registrations.Add(FRegistrant{ InOuterRegister, InInnerRegister, InPackageName, &InInfo, DeprecatedFieldInfo });
    

    这里有个小问题,我们之前使用的是FClassDeferredRegistry::Get().AddRegistration

    为什么这里是TDeferredRegistry::AddRegistration?

    FClassDeferredRegistry 定义如下

    1. using FClassDeferredRegistry = TDeferredRegistry;
    2. using FEnumDeferredRegistry = TDeferredRegistry;
    3. using FStructDeferredRegistry = TDeferredRegistry;
    4. using FPackageDeferredRegistry = TDeferredRegistry;

    可以看出TDeferredRegistry是个模板类,而FClassDeferredRegistry是模板进行了实例化。因为我们这里处理的是Class,所以使用的是FClassDeferredRegistry,同级别的其他实例化用于处理Enum Struct等。

    到这里,StaticClass被构造为FRegistrant,添加进数组Registrations里面去了。

    FRegistrant长这样

    1. /**
    2. * Maintains information about a pending registration
    3. */
    4. struct FRegistrant
    5. {
    6. TType* (*OuterRegisterFn)();
    7. TType* (*InnerRegisterFn)();
    8. const TCHAR* PackageName;
    9. TInfo* Info;
    10. #if WITH_RELOAD
    11. TType* OldSingleton;
    12. #endif
    13. FFieldCompiledInInfo* DeprecatedFieldInfo;
    14. #if WITH_RELOAD
    15. bool bHasChanged;
    16. #endif
    17. };

    这里StaticClass变为了InnerRegisterFn。

    所以,问题进一步变为,什么时候使用Registrations里面的InnerRegisterFn?

    因为Registrations是个成员变量,我们注意到TDeferredRegistry有个接口返回了它

    1. /**
    2. * Return the collection of registrations
    3. */
    4. TArray& GetRegistrations()
    5. {
    6. return Registrations;
    7. }

    并且有一个地方使用这个接口干了可疑的事情

    1. /** Register all loaded classes */
    2. void UClassRegisterAllCompiledInClasses()
    3. {
    4. #if WITH_RELOAD
    5. TArray AddedClasses;
    6. #endif
    7. SCOPED_BOOT_TIMING("UClassRegisterAllCompiledInClasses");
    8. FClassDeferredRegistry& Registry = FClassDeferredRegistry::Get();
    9. Registry.ProcessChangedObjects();
    10. for (const FClassDeferredRegistry::FRegistrant& Registrant : Registry.GetRegistrations())
    11. {
    12. UClass* RegisteredClass = FClassDeferredRegistry::InnerRegister(Registrant);
    13. #if WITH_RELOAD
    14. if (IsReloadActive() && Registrant.OldSingleton == nullptr)
    15. {
    16. AddedClasses.Add(RegisteredClass);
    17. }
    18. #endif
    19. }
    20. ......
    21. }

    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调用成功。

  • 相关阅读:
    Assigning a Static IP Address to a WSL2 Distribution
    [plugin:vite:css] variable @base-color is undefined
    Hive的基本SQL操作(DDL篇)
    面试总结之并发编程
    【C++Primer---C++知识点记录*V---IO库】
    LiteIDE主题定制教程
    Vue使用阿里iconfont图标
    JUC源码学习笔记6——ReentrantReadWriteLock
    算法打卡 Day19(二叉树)-平衡二叉树 + 二叉树的所有路径 + 左叶子之和 + 完全二叉树的节点个数
    ChatGPT推出新“朗读”功能 支持多语言与声音;使用大型语言模型增强分类数据集
  • 原文地址:https://blog.csdn.net/iaibeyond/article/details/126202080