UE4反射系统-前言
引言学习UE引擎会不可避免地接触到它的反射系统,而这也是UE代码区别于传统C++的一大语言特征。同时,反射系统也是UE用来构建编辑器和蓝图虚拟机的重要基石之一。
目前关于UE反射系统已有很多文章,但笔者在阅读这些文章的过程中,很容易迷失在繁复的语法细节以及茫茫多的宏定义中,忘记原本的主体脉络,最终陷入“看完了,代码也跟着读下来了,没有问题,但好像又什么都没看懂”的迷惑中。
因此本文尝试以人的思维习惯重新梳理UE4反射系统的流程,以运行时反射系统构建的入口代码开始,逐步了解构建过程并反推UHT生成代码的必要性。笔者对于UE也是初学,抱着边学边梳理的想法写下本文,如有错漏之处欢迎指摘。下面进入正题
整体脉络
反射系统的构建离不开下面三个步骤:
[*]编译生成。该步骤依赖于UHT(Unreal Header Tools),在引擎编译过程中,UHT会自动分析代码、为我们生成类型系统需要的中间文件并最终include到对应代码的头文件以及源文件中。
[*]注册&收集。该步骤在运行时,main函数执行之前或者新module被加载时执行,借助上一步骤中生成的代码将类信息注册到全局的信息收集表中。
[*]类型系统构建。该步骤在对应模块被加载时执行,模块的加载由引擎的Init或者新module被引用时触发(新module加载会针对该module中的代码重新执行一次步骤2)。
步骤1的中间代码生成细节我们无需深究,因此可以直接从步骤2开始。
在开始看代码之前,我们先自定义一个派生自UObject且不含任何变量和函数的空类,以简化阅读干扰,经过编译后,可以看到代码里自动多出了一些include以及一行GENERATED_BODY的宏
#pragma once
#include "UObject/NoExportTypes.h"
#include "MirrorClass.generated.h"
UCLASS()
class COOPGAME_API UMirrorClass : public UObject
{
GENERATED_BODY()
};
许多读者应该已经知道,类型系统的运行时触发依赖于C++ Static对象初始化,即Static对象的初始化会在main函数执行之前完成,类型系统正是依赖于这个特性保证足够早地完成构建。
为了避免一开始就陷入宏定义的汪洋大海中,我们这里先不对GENERATED_BODY进行展开分析,只需要先默认这里会关联到UHT自动生成的中间文件即可。这里我们先直接给出反射数据注册的入口,共有两处:
1. TClassCompiledInDefer
该结构是IMPLEMENT_CLASS宏定义中的Static对象
// 代码摘自UE4.18版本, ObjectMacros.h
// Register a class at startup time.
#define IMPLEMENT_CLASS(TClass, TClassCrc) \
// 注: 就是这里!就是这个Static对象
static TClassCompiledInDefer<TClass> AutoInitialize##TClass(TEXT(#TClass), sizeof(TClass), TClassCrc);// \
UClass* TClass::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(#TClass) + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0), \
PrivateStaticClass, \
StaticRegisterNatives##TClass, \
sizeof(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 PrivateStaticClass; \
}
IMPLEMENT_CLASS会在UHT生成的类对应中间cpp文件中被调用,例如类名叫MirrorClass,那么生成的对应中间文件分别为MirrorClass.generated.h以及MirrorClass.gen.cpp,而IMPLEMENT_CLASS的调用就在MirrorClass.gen.cpp中。
// 中间文件相对工程目录的存放路径如下
Intermediate\Build\Win64\UE4Editor\Inc\{PROJECT_NAME}
2. FCompiledInDefer
在MirrorClass.gen.cpp中,还定义了另外一个静态Struct,即FCompiledInDefer,一般来说位于该文件的末尾
IMPLEMENT_CLASS(UMirrorClass, 2947269641);
// 这里是另一个Static的Struct
static FCompiledInDefer Z_CompiledInDefer_UClass_UMirrorClass(Z_Construct_UClass_UMirrorClass, &UMirrorClass::StaticClass, TEXT(&#34;/Script/CoopGame&#34;), TEXT(&#34;UMirrorClass&#34;), false, nullptr, nullptr, nullptr);
DEFINE_VTABLE_PTR_HELPER_CTOR(UMirrorClass);
这两个Static的Struct会在运行时自动构造,其构造函数也就是类型系统的万物起源了。如此一来,就等于找到了迷宫的核心,而我们的目标就是后续弄清楚这两个Static对象的初始化过程到底做了什么以及为什么这么做。
Tips
[*]时刻牢记,反射系统要构建的是一个UClass对象,这个对象用于描述我们的目标类。由于反射系统涉的目的是要构建一个能够描述目标类的对象,这本身就是一个有点绕的话,因此在看代码的时候很容易被绕进去。
[*]相关代码里有很多HotReload相关的逻辑,在刚开始接触的时候可以直接忽略这部分内容,降低理解成本。即直接无视#if WITH_HOT_RELOAD与#endif中间的代码块。
后续文章
页:
[1]