千里共如何
微风吹兰杜
上一篇中已经展开了类型生成文件中的宏,并较为详细地分析了生成代码中的类型注册信息。这里接着上面的三个疑问,继续进行探讨。
类型信息静态自动收集
C++ 静态自动注册
这里说的静态自动注册,指的是通过定义静态全局变量的方式,直接在该变量的构造函数里完成注册。
举个简单的例子,如果想在 main()
函数执行前,自动启动部分功能,使得 main()
函数依赖的部分逻辑能提前准备好,则可以将这部分逻辑写在static变量的构造函数中。
#include <iostream>
class A {
public:
bool ready;
A() {
ready = true;
}
};
const static A a;
int main() {
if (a.ready) {
printf("OK\n");
} else {
printf("Not ready\n");
exit(-1);
}
return 0;
}
程序输出:
UE收集类型信息(反射信息)也是采用同样的方式,在 UE4中C++类型生成文件分析 中已经提到两个静态全局变量:
static TClassCompiledInDefer<UHH> AutoInitializeUHH(TEXT("UHH"), sizeof(UHH), 1368286490);
static FCompiledInDefer Z_CompiledInDefer_UClass_UHH(Z_Construct_UClass_UHH, &UHH::StaticClass, TEXT("/Script/CCReflection"), TEXT("UHH"), false, nullptr, nullptr, nullptr);
就是出于同样的目的,当然生成代码中随处可见这样的使用姿势。
需要说明的是,不应该依赖于不同文件中的静态变量的初始化顺序,因为C++标准中没有规定不同文件中静态变量的初始化顺序,这样一旦出现bug,将极难定位问题,因为不同编译器甚至同种但不同版本的编译器的处理都不一样(菜鸡如我就查过此类问题)。
何处生成UClass 对象
继续探讨在 UE4中C++类型生成文件分析 中的三个疑问:
- ` ClassParams
最终用于生成
UClass对象吗?是的话,什么时候生成,在哪里生成?与调用
StaticClass()返回
UClass` 对象是什么关系? - 函数指针的收集是如何完成的?
- 如何利用两个全局静态变量的构造函数?
现在看来这三个问题很可能是同一问题,继续带着疑问去看静态自动注册的过程。之前已经提到,Z_Construct_UClass_UHH_Statics
结构体的信息会被注册到ClassParams
静态成员中,ClassParams
的处理就决定了信息收集的下一步走向。
ClassParams
静态成员实际会用于在 Z_Construct_UClass_UHH()
函数中生成 UClass
对象。
UClass* Z_Construct_UClass_UHH()
{
static UClass* OuterClass = nullptr;
if (!OuterClass)
{
UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_UHH_Statics::ClassParams);
}
return OuterClass;
}
不巧的是,StaticClass()
-> GetPrivateStaticClass()
也会生成一个 UClass
对象。
UClass* UHH::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("UHH") + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0),
PrivateStaticClass,
...
);
}
return PrivateStaticClass;
}
这里 PrivateStaticClass
和 OuterClass
是同一个吗?
其实在 UE4中C++类型生成文件分析 中已经有埋坑了,生成 ClassParams
静态对象的时候就已经把StaticClass()
指针传进去了,分析引擎中 UE4CodeGen_Private::ConstructUClass()
实现,就发现会先调用 StaticClass()
得到一个 UClass
对象,然后再进行值的注册。不过我猜测, StaticClass()
的第一次调用并不是在这,这里只要确定 PrivateStaticClass
和 OuterClass
是同一个就好了。
延迟注册
UE4反射类型信息延迟注册
继续研究 TClassCompiledInDefer
和 FCompiledInDefer
:
struct TClassCompiledInDefer : public FFieldCompiledInInfo
{
TClassCompiledInDefer(const TCHAR* InName, SIZE_T InClassSize, uint32 InCrc)
: FFieldCompiledInInfo(InClassSize, InCrc)
{
UClassCompiledInDefer(this, InName, InClassSize, InCrc);
}
virtual UClass* Register() const override
{
LLM_SCOPE(ELLMTag::UObject);
return TClass::StaticClass();
}
virtual const TCHAR* ClassPackage() const override
{
return TClass::StaticPackage();
}
};
struct FCompiledInDefer
{
FCompiledInDefer(class UClass *(*InRegister)(), class UClass *(*InStaticClass)(), const TCHAR* PackageName, const TCHAR* Name, bool bDynamic, const TCHAR* DynamicPackageName = nullptr, const TCHAR* DynamicPathName = nullptr, void (*InInitSearchableValues)(TMap<FName, FName>&) = nullptr)
{
if (bDynamic)
{
GetConvertedDynamicPackageNameToTypeName().Add(FName(DynamicPackageName), FName(Name));
}
UObjectCompiledInDefer(InRegister, InStaticClass, Name, PackageName, bDynamic, DynamicPathName, InInitSearchableValues);
}
};
进一步查看 UClassCompiledInDefer()
和 UObjectCompiledInDefer()
的实现:
void UClassCompiledInDefer(FFieldCompiledInInfo* ClassInfo, const TCHAR* Name, SIZE_T ClassSize, uint32 Crc)
{
...
GetDeferredClassRegistration().Add(ClassInfo); // 得到static对象,数组
...
}
void UObjectCompiledInDefer(UClass *(*InRegister)(), UClass *(*InStaticClass)(), const TCHAR* Name, const TCHAR* PackageName, bool bDynamic, const TCHAR* DynamicPathName, void (*InInitSearchableValues)(TMap<FName, FName>&))
{
...
TArray<UClass *(*)()>& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration(); // 得到static对象
checkSlow(!DeferredCompiledInRegistration.Contains(InRegister));
DeferredCompiledInRegistration.Add(InRegister); // static
...
}
按照顺序分析,可以总结出TClassCompiledInDefer
和 FCompiledInDefer
静态初始化的大致作用:
- 前者将自己本身注册到一个数组中,并提供了
Register()
方法返回UClass
对象(可能是创建UClass
对象) - 后者将前面说到的指向
Z_Construct_UClass_UHH()
函数的指针,添加到了一个数组中,用于在可能已经创建的UClass
对象上,注册之前已经收集好的反射类型信息(ClassParams
)
以目前收集到的资料以及我对源码的理解,解释下这样做的目的:
- 就像之前说到的,
Z_Construct_UClass_UHH()
中不一定会创建UClass
对象,而FCompiledInDefer
作用就是将Z_Construct_UClass_UHH()
作为回调注册到数组中,Z_Construct_UClass_UHH()
就负责添加反射信息到UClass
对象上;而创建UClass
对象的工作,应该是由TClassCompiledInDefer
提供的Register()
完成的; - 延迟注册是因为UE4中要注册的
UClass
对象较多,如果全部要在main()
函数前注册完成,会导致编辑器实际逻辑不能立即执行,会让用户觉得启动很慢。所以先把注册的回调都传入到数组中,在main()
函数开始执行后,再调用注册回调执行相应的类型信息注册逻辑。
进行一下总结,就可以解答之前的三个疑问了:
- ` ClassParams
应该是用于在已经生成的
UClass对象上注册信息,而不是生成
UClass对象;
UClass对象由
TClassCompiledInDefer提供的
Register()来生成,而
Register()` 会在编辑器的启动控制下延迟调用 - 函数指针的收集,由
TClassCompiledInDefer
提供的Register()
调用StaticClass()
注册,Register()
调用时机前面已经解释了 - 见对
TClassCompiledInDefer
和FCompiledInDefer
静态初始化的分析
类型信息收集流程梳理
回忆前面的生成文件的分析,可以将整个收集过程整理一下(属性和函数信息的收集)。
- 属性信息
- 定义
UE4CodeGen_Private::FUnsizedIntPropertyParams
类型的静态成员 - 将静态成员添加到
UE4CodeGen_Private::FPropertyParamsBase*
类型的指针的数组中 - 指针数组作为参数,生成
UE4CodeGen_Private::FClassParams
类型的ClassParams
ClassParams
作为参数,用于给UClass
收集信息,该收集动作发生于Z_Construct_UClass_XXX()
函数的调用FCompiledInDefer
类型的Z_CompiledInDefer_UClass_XXX
静态初始化的时候,将Z_Construct_UClass_XXX()
的函数指针传入,并将该指针添加到DeferredCompiledInRegistration
数组中
- 定义
- 函数信息
- 函数指针收集
- 定义统一签名的函数
execXXX()
,并在其实现中调用实际XXX()
函数 - 提供
XXX::StaticRegisterNativesXXX()
函数,将函数execXXX()
的名字指向指针的映射添加到了对应UClass
对象的NativeFunctionLookupTable
的数组成员中 XXX::StaticRegisterNativesXXX
函数也会通过函数指针,传入GetPrivateStaticClassBody()
函数,用于创建XXX
对应的UClass
对象- 在第一次调用
UHH::StaticClass()
的时候会真正执行 ”映射添加到NativeFunctionLookupTable
的数组成员中“ 的逻辑 - 这里猜测:第一次调用
UHH::StaticClass()
应该是由引擎启动时,调用TClassCompiledInDefer
提供的Register()
完成的
- 定义统一签名的函数
- 函数签名信息的收集(用于生成
UFunction
对象)- 参数和返回值生成各自的
UE4CodeGen_Private::FUnsizedIntPropertyParams
类型的静态成员 - 函数的参数和返回值的静态成员,通过指针存储到
UE4CodeGen_Private::FPropertyParamsBase*
类型的PropPointers
静态指针数组中 - 静态指针数组将用于静态初始化
UE4CodeGen_Private::FFunctionParams
类型的FuncParams
静态成员 FuncParams
静态成员在Z_Construct_UFunction_XXX_XXXX()
函数中用于生成UFunction
对象Z_Construct_UFunction_XXX_XXXX()
函数同样以函数指针的形式添加到FClassFunctionLinkInfo
类型的FuncInfo
数组中FuncInfo
数组用于生成UE4CodeGen_Private::FClassParams
类型的ClassParams
ClassParams
作为参数,用于给UClass
收集信息,该收集动作发生于Z_Construct_UClass_XXX()
函数的调用FCompiledInDefer
类型的Z_CompiledInDefer_UClass_UHH
静态初始的时候,将Z_Construct_UClass_XXX()
的函数指针传入,并将该指针添加到DeferredCompiledInRegistration
数组中
- 参数和返回值生成各自的
- 函数指针收集
联系延迟注册的内容,可以将类型信息注册( UClass
完整注册)分成两部分
- 生成类对应的
UClass
对象 - 收集所有反射信息,在已存在的
UClass
对象完成信息填充
需要注意的是,Native 函数指针收集在生成 UClass
对象时进行,即第一次调用 StaticClass()
函数的时候就执行了。
生成UClass
对象的流程:
往UClass
对象上填充信息的流程:
现在明确了 UClass
对象创建的方式,创建的位置;同时也明确了往 UClass
填充信息的方式,填充的位置。而未确定的是执行这两个动作的时间,从之前的分析来看,应该在编辑器启动以后,也就是 mian()
函数开始执行以后。