千里共如何
微风吹兰杜
上一篇中已经展开了类型生成文件中的宏,并较为详细地分析了生成代码中的类型注册信息。这里接着上面的三个疑问,继续进行探讨。
类型信息静态自动收集
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类型的ClassParamsClassParams作为参数,用于给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() 函数开始执行以后。