前言

UE 补完计划(一) 反射-1

上文我们分析了 UHT 针对一个类的生成代码,基本了解了 UE 中反射的实现原理,这节我们就来分析一下这些反射信息是如何注册到系统中的。

反射信息注册

ue中反射信息是分段注册的,由static结构体的构造为第一段,执行在main函数之前,第二段在ue启动时完成真正的注册。
反射的信息几乎都在对应类的gen.cpp文件中,这也是由UHT生成的。

gen.cpp文件中ue会生成一个每个类唯一的FClassRegistrationInfo变量,这个变量与GetPrivateStaticClass函数定义在gen.cpp文件中的IMPLEMENT_CLASS_NO_AUTO_REGISTRATION宏中,这个变量就包含了类的信息。

1
2
// gen.cpp
IMPLEMENT_CLASS_NO_AUTO_REGISTRATION(UMyObject);

查看这个宏的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Implement the GetPrivateStaticClass and the registration info but do not auto register the class.  
// This is primarily used by UnrealHeaderTool
#define IMPLEMENT_CLASS_NO_AUTO_REGISTRATION(TClass) \
FClassRegistrationInfo Z_Registration_Info_UClass_##TClass; \
UClass* TClass::GetPrivateStaticClass() \
{ \
if (!Z_Registration_Info_UClass_##TClass.InnerSingleton) \
{ \
/* this could be handled with templates, but we want it external to avoid code bloat */ \
GetPrivateStaticClassBody( \
StaticPackage(), \
(TCHAR*)TEXT(#TClass) + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0), \
Z_Registration_Info_UClass_##TClass.InnerSingleton, \
StaticRegisterNatives##TClass, \
sizeof(TClass), \
alignof(TClass), \
(EClassFlags)TClass::StaticClassFlags, \
TClass::StaticClassCastFlags(), \
TClass::StaticConfigName(), \
(UClass::ClassConstructorType)InternalConstructor<TClass>, \
(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>, \
&TClass::AddReferencedObjects, \
&TClass::Super::StaticClass, \
&TClass::WithinClass::StaticClass \
); \
} \
return Z_Registration_Info_UClass_##TClass.InnerSingleton; \
}

上节分析过,GetPrivateStaticClass() 就是 StaticClass() 的具体实现,而这个函数的本质是在第一次调用时通过GetPrivateStaticClassBody函数生成一个uclassZ_Registration_InfoUClass##TClass.InnerSingleton参数。

1
2
3
4
5
/** Returns a UClass object representing this class at runtime */ \
inline static UClass* StaticClass() \
{ \
return GetPrivateStaticClass(); \
} \

GetPrivateStaticClassBody()

查看 GetPrivateStaticClassBody() 的定义(class.cpp):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
void GetPrivateStaticClassBody(
const TCHAR* PackageName,
const TCHAR* Name,
UClass*& ReturnClass,
void(*RegisterNativeFunc)(),
uint32 InSize,
uint32 InAlignment,
EClassFlags InClassFlags,
EClassCastFlags InClassCastFlags,
const TCHAR* InConfigName,
UClass::ClassConstructorType InClassConstructor,
UClass::ClassVTableHelperCtorCallerType InClassVTableHelperCtorCaller,
UClass::ClassAddReferencedObjectsType InClassAddReferencedObjects,
UClass::StaticClassFunctionType InSuperClassFn,
UClass::StaticClassFunctionType InWithinClassFn
)
{
#if WITH_RELOAD
if (IsReloadActive() && GetActiveReloadType() != EActiveReloadType::Reinstancing)
{
UPackage* Package = FindPackage(NULL, PackageName);
if (Package)
{
ReturnClass = FindObject<UClass>((UObject *)Package, Name);
if (ReturnClass)
{
if (ReturnClass->HotReloadPrivateStaticClass(
InSize,
InClassFlags,
InClassCastFlags,
InConfigName,
InClassConstructor,
InClassVTableHelperCtorCaller,
InClassAddReferencedObjects,
InSuperClassFn(),
InWithinClassFn()
))
{
// Register the class's native functions.
RegisterNativeFunc();
}
return;
}
else
{
UE_LOG(LogClass, Log, TEXT("Could not find existing class %s in package %s for reload, assuming new or modified class"), Name, PackageName);
}
}
else
{
UE_LOG(LogClass, Log, TEXT("Could not find existing package %s for reload of class %s, assuming a new package."), PackageName, Name);
}
}
#endif

ReturnClass = (UClass*)GUObjectAllocator.AllocateUObject(sizeof(UClass), alignof(UClass), true);
ReturnClass = ::new (ReturnClass)
UClass
(
EC_StaticConstructor,
Name,
InSize,
InAlignment,
InClassFlags,
InClassCastFlags,
InConfigName,
EObjectFlags(RF_Public | RF_Standalone | RF_Transient | RF_MarkAsNative | RF_MarkAsRootSet),
InClassConstructor,
InClassVTableHelperCtorCaller,
InClassAddReferencedObjects
);
check(ReturnClass);

InitializePrivateStaticClass(
InSuperClassFn(),
ReturnClass,
InWithinClassFn(),
PackageName,
Name
);

// Register the class's native functions.
RegisterNativeFunc();
}

这个函数的逻辑分为 WITH_RELOAD 即热重载部分与编译部分,如果是热重载则查找已存在的类并且调用 HotReloadPrivateStaticClass,如果不是则通过GUObjectAllocator.AllocateUObject分配空间并构造一个UClass,然后由InitializePrivateStaticClass 进行注册。

InitializePrivateStaticClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Shared function called from the various InitializePrivateStaticClass functions generated my the IMPLEMENT_CLASS macro.
*/
COREUOBJECT_API void InitializePrivateStaticClass(
class UClass* TClass_Super_StaticClass,
class UClass* TClass_PrivateStaticClass,
class UClass* TClass_WithinClass_StaticClass,
const TCHAR* PackageName,
const TCHAR* Name
)
{
TRACE_LOADTIME_CLASS_INFO(TClass_PrivateStaticClass, Name);
NotifyRegistrationEvent(PackageName, Name, ENotifyRegistrationType::NRT_Class, ENotifyRegistrationPhase::NRP_Started);

/* No recursive ::StaticClass calls allowed. Setup extras. */
if (TClass_Super_StaticClass != TClass_PrivateStaticClass)
{
TClass_PrivateStaticClass->SetSuperStruct(TClass_Super_StaticClass);
}
else
{
TClass_PrivateStaticClass->SetSuperStruct(NULL);
}
TClass_PrivateStaticClass->ClassWithin = TClass_WithinClass_StaticClass;

// Register the class's dependencies, then itself.
TClass_PrivateStaticClass->RegisterDependencies();
{
// Defer
TClass_PrivateStaticClass->Register(PackageName, Name);
}

NotifyRegistrationEvent(PackageName, Name, ENotifyRegistrationType::NRT_Class, ENotifyRegistrationPhase::NRP_Finished);
}

这里我们先跳过函数的前两句,可以看到该函数先是设置了传入类的父子关系,接着按照先父类再自身的顺序进行注册,非常合理。RegisterDependencies() 是一个递归的函数,会层层注册父类直到 SuperStruct == NULL

1
2
3
4
5
6
7
8
9
10
11
/**
* Force any base classes to be registered first, then call BaseRegister
*/
void UStruct::RegisterDependencies()
{
Super::RegisterDependencies();
if (SuperStruct != NULL)
{
SuperStruct->RegisterDependencies();
}
}

之后是注册自己,TClass_PrivateStaticClass->Register(PackageName, Name);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/** Enqueue the registration for this object. */
void UObjectBase::Register(const TCHAR* PackageName,const TCHAR* InName)
{
TMap<UObjectBase*, FPendingRegistrantInfo>& PendingRegistrants = FPendingRegistrantInfo::GetMap();

FPendingRegistrant* PendingRegistration = new FPendingRegistrant(this);
PendingRegistrants.Add(this, FPendingRegistrantInfo(InName, PackageName));

#if USE_PER_MODULE_UOBJECT_BOOTSTRAP
if (FName(PackageName) != FName("/Script/CoreUObject"))
{
TMap<FName, TArray<FPendingRegistrant*>>& PerModuleMap = GetPerModuleBootstrapMap();

PerModuleMap.FindOrAdd(FName(PackageName)).Add(PendingRegistration);
}
else
#endif
{
if (GLastPendingRegistrant)
{
GLastPendingRegistrant->NextAutoRegister = PendingRegistration;
}
else
{
check(!GFirstPendingRegistrant);
GFirstPendingRegistrant = PendingRegistration;
}
GLastPendingRegistrant = PendingRegistration;
}
}

Register 函数中,可以看到两种数据结构:静态链表与静态 Map,负责信息的简单记录,Map 是为了查找方便,链表是为了保证注册顺序。这其中的信息会在所有Register完成之后,再使用添加到真正的引擎查找使用的全局 Map 中。

这两种结构都定义在 UObjectBase.cpp 文件的上方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/** Objects to automatically register once the object system is ready.					*/
struct FPendingRegistrantInfo
{
const TCHAR* Name;
const TCHAR* PackageName;
FPendingRegistrantInfo(const TCHAR* InName,const TCHAR* InPackageName)
: Name(InName)
, PackageName(InPackageName)
{}
static TMap<UObjectBase*, FPendingRegistrantInfo>& GetMap()
{
static TMap<UObjectBase*, FPendingRegistrantInfo> PendingRegistrantInfo;
return PendingRegistrantInfo;
}
};

/** Objects to automatically register once the object system is ready. */
struct FPendingRegistrant
{
UObjectBase* Object;
FPendingRegistrant* NextAutoRegister;
FPendingRegistrant(UObjectBase* InObject)
: Object(InObject)
, NextAutoRegister(NULL)
{}
};
static FPendingRegistrant* GFirstPendingRegistrant = NULL;
static FPendingRegistrant* GLastPendingRegistrant = NULL;

RegisterNativeFunc

最后调用传入的RegisterNativeFunc函数(传入的这一参数为函数指针)。RegisterNativeFuncgen.cpp文件中的StaticRegisterNatives开头加类名的函数,作用是注册类内拥有UFUNCTION宏的函数。

对应到案例中就是下面的函数:

1
2
3
4
5
6
7
8
void UMyObject::StaticRegisterNativesUMyObject()
{
UClass* Class = UMyObject::StaticClass();
static const FNameNativePtrPair Funcs[] = {
{ "ffffffunc", &UMyObject::execffffffunc },
};
FNativeFunctionRegistrar::RegisterFunctions(Class, Funcs, UE_ARRAY_COUNT(Funcs));
}

注册时间

由上文我们分析出,在 GetPrivateStaticClassBody() 中会通过层层调用将类信息传递给反射系统,UE 中反射信息注册的核心逻辑还是通过 static 构造函数来在全局 main 函数之前,执行反射系统的收集逻辑。

image.png

回到 gen.cpp

接下来我们来看ue是怎么利用static变量的初始化进行注册的,让我们回到 gen.cpp,来看看剩下的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
UClass* Z_Construct_UClass_UMyObject()
{
if (!Z_Registration_Info_UClass_UMyObject.OuterSingleton)
{
UECodeGen_Private::ConstructUClass(Z_Registration_Info_UClass_UMyObject.OuterSingleton, Z_Construct_UClass_UMyObject_Statics::ClassParams);
}
return Z_Registration_Info_UClass_UMyObject.OuterSingleton;
}

struct Z_CompiledInDeferFile_FID_MyProject_Source_MyProject_MyObject_h_Statics
{
static const FClassRegisterCompiledInInfo ClassInfo[];
};

const FClassRegisterCompiledInInfo Z_CompiledInDeferFile_FID_MyProject_Source_MyProject_MyObject_h_Statics::ClassInfo[] = {
{ Z_Construct_UClass_UMyObject, UMyObject::StaticClass, TEXT("UMyObject"),
&Z_Registration_Info_UClass_UMyObject,
CONSTRUCT_RELOAD_VERSION_INFO(FClassReloadVersionInfo,
sizeof(UMyObject), 2131145841U) },
};

static FRegisterCompiledInInfo Z_CompiledInDeferFile_FID_MyProject_Source_MyProject_MyObject_h_3902271098(
TEXT("/Script/MyProject"),
Z_CompiledInDeferFile_FID_MyProject_Source_MyProject_MyObject_h_Statics::ClassInfo,
UE_ARRAY_COUNT(Z_CompiledInDeferFile_FID_MyProject_Source_MyProject_MyObject_h_Statics::ClassInfo),
nullptr, 0,
nullptr, 0
);

Z_Construct_UClass_UMyObject 用于返回这个类的 OuterSingleton 单例,在后文的分析中我们会知道,这是包含一个类的完整信息的类实例;CassInfo[] 会收集类的信息并构造一个列表,这里可能是由于案例中仅有 MyObject 一个类,因此 ClassInfo[] 中仅有一个成员;而 Z_CompiledInDeferFile_FID_MyProject_Source_MyProject_MyObject_h_3902271098 则是利用 CassInfo[] 的信息构造了另一个对象,我们依次分析。

FRegisterCompiledInInfo

这个类定义在 UObjectBase.h 中,实际上是转发了所有的参数给 RegisterCompiledInInfo(),这个函数在 UObjectBase.cpp 中有多个重载。

1
2
3
4
5
6
7
8
9
10
11
/**
* Helper class to perform registration of object information. It blindly forwards a call to RegisterCompiledInInfo
*/
struct FRegisterCompiledInInfo
{
template <typename ... Args>
FRegisterCompiledInInfo(Args&& ... args)
{
RegisterCompiledInInfo(std::forward<Args>(args)...);
}
};

先来看看 Z_CompiledInDeferFile_FID_MyProject_Source_MyProject_MyObject_h_3902271098 调用的这个重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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);
}
}

可以看出这个函数就是将 ClassInfoStructInfoEnumInfo 中的类或者结构体或枚举体等信息分别交给不同的 RegisterCompiledInInfo 来处理,由于我们定义的是一个 class,因此会转入下面这个重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}

这里可以看出,函数调用了 FClassDeferredRegistry::Get().AddRegistration()InOuterRegisterInInnerRegister
等信息传递给了 Registrations,查看这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    /// <summary>
/// Adds the given registration information for the given object. Objects are either classes, structs, or enumerations.
/// </summary>
/// <param name="InOuterRegister">Returns a fully initialize instance of the object</param>
/// <param name="InInnerRegister">Returns an allocated but uninitialized instance of the object. This is used only by UClass.</param>
/// <param name="InPackageName">Name of the package</param>
/// <param name="InName">Name of the object</param>
/// <param name="InInfo">Persistent information about the object</param>
/// <param name="InVersion">Version information for this incarnation of the object</param>
/// <param name="DeprevatedFieldInfo">Deprecated factory object for the inner register for UClass. Either InInnerRegister or this value must be specified for UClass</param>
bool 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
}

AddRegistration 函数的逻辑是现在自身 map 上找一下是否存在要add的类,如果有则看下是否为热重载,如果没有则加入map,最后在把信息包装成FRegistrant放入自身的Registrations数组中,待后续使用。

可以看到注释中关于 innerouter 的解释:

1
2
/// <param name="InOuterRegister">Returns a fully initialize instance of the object</param>
/// <param name="InInnerRegister">Returns an allocated but uninitialized instance of the object. This is used only by UClass.</param>

innerouter

在了解了这两个注册函数的区别后,来看看参数到底是怎么传的,回溯一下漫长的调用链,会发现如下的对应关系:

1
2
3
4
5
6
7
8
9
10
11
/**
* Composite class register compiled in info
*/
struct FClassRegisterCompiledInInfo
{
class UClass* (*OuterRegister)();
class UClass* (*InnerRegister)();
const TCHAR* Name;
FClassRegistrationInfo* Info;
FClassReloadVersionInfo VersionInfo;
};
1
2
3
4
5
6
7
8
9
const FClassRegisterCompiledInInfo Z_CompiledInDeferFile_FID_MyProject_Source_MyProject_MyObject_h_Statics::ClassInfo[] = {
{
Z_Construct_UClass_UMyObject, // OuterRegister
UMyObject::StaticClass, // InnerRegister
TEXT("UMyObject"),
&Z_Registration_Info_UClass_UMyObject,
CONSTRUCT_RELOAD_VERSION_INFO(FClassReloadVersionInfo, sizeof(UMyObject), 2131145841U)
},
};

可以看出,Z_Construct_UClass_##ClassName 包含了完整的类的信息,而 ClassName::StaticClass 则是一个分配了空间但没有完整初始化的类实例。

inner 注册

inner 注册发生在CoreNative.cppStartupModule函数中,函数会依次处理这些收集到的类信息:

1
2
3
4
5
6
7
virtual void StartupModule() override
{
// Register all classes that have been loaded so far. This is required for CVars to work.
UClassRegisterAllCompiledInClasses();

// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/** 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
}

#if WITH_RELOAD
if (AddedClasses.Num() > 0)
{
FCoreUObjectDelegates::ReloadAddedClassesDelegate.Broadcast(AddedClasses);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FCoreUObjectDelegates::RegisterHotReloadAddedClassesDelegate.Broadcast(AddedClasses);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
#endif
}

outer 注册

1
2
3
4
5
6
7
8
UClass* Z_Construct_UClass_UMyObject()
{
if (!Z_Registration_Info_UClass_UMyObject.OuterSingleton)
{
UECodeGen_Private::ConstructUClass(Z_Registration_Info_UClass_UMyObject.OuterSingleton, Z_Construct_UClass_UMyObject_Statics::ClassParams);
}
return Z_Registration_Info_UClass_UMyObject.OuterSingleton;
}

TODO: 这部分具体流程还没有梳理完