转自:
https://blog.csdn.net/mohuak/article/details/81913532
https://blog.csdn.net/u012999985/article/details/52902065

1. UE4反射系统

什么是反射系统
在UE4里面,你无时无刻都会看到类似UFUNCTION()这样的宏。官方文档告诉你,只要在一个函数的前面加上这个宏,然后在括号里面加上BlueprintCallable就可以在编辑器里面调用了。按照他的指示,我们就能让我们的函数实现各种各样特别的功能,那这个效果就是通过UE4的反射系统来实现的。
其实,所谓反射,是程序在运行时进行自检的一种能力,自检什么呢?我认为就是检查自己的C++类,函数,成员变量,结构体等等(对应起来也就是大家在UE4能看到的UCLASS,UFUNCTON,UPROPERTY,USTRUCT后面还会提到)。
那检查这些东西做什么呢?最明显的就是支持蓝图和C++的交互功能,说的更通俗一点,就是可以更自由的控制这些结构,让他在我们想出现的地方出现,让他在我们想使用的地方使用。要知道我们在虚幻4中声明的任意一个类,都是继承于UObject类的,所以他远远不是我们所以为的那个普通的C++类。我们可以使用这个类进行网络复制,执行垃圾回收,让他和蓝图交互等等。而这一切原生的C++是并不支持的,也正是因此虚幻4才构建了一个这样的反射系统。

2. 反射实现机制和基本原理

在了解反射系统前,我们必须要知道两个UE4独特的文件类型—“.generate.h”以及“.generate.cpp”。“.generate.h”文件在每一个类的声明前面都会被包含到对应的头文件里面。(这也是官方建议我们要用编辑器来创建类的原因,他们并不是常规的C++类)而“.generate.cpp”对于一整个项目只会有一个。这两种文件可以说是反射系统的关键所在,他们是通过Unreal Build Tool(UBT) 和UnrealHeaderTool(UHT)来生成的。

2.1 UBT 和UHT
UnrealBuildTool(UBT,C#):UE4的自定义工具,来编译UE4的逐个模块并处理依赖等。我们编写的Target.cs,Build.cs都是为这个工具服务的。
UnrealHeaderTool (UHT,C++):UE4的C++代码解析生成工具,我们在代码里写的那些宏UCLASS等和#include “*.generated.h”都为UHT提供了信息来生成相应的C++反射代码。
代码编译在两个阶段中进行:1.UHT 被调用。它将解析 C++ 头中引擎相关类元数据,并生成自定义代码,以实现诸多 UObject 相关的功能。2.普通 C++ 编译器被调用,以便对结果进行编译。)
思考:UHT如何实现反射?

打个比方:UClass其实就好比一张表,一张户口本的东西,指向”真实家庭“的指针。上面记录着一些信息,好比:

张三:

1995年出生
李四:

1991年出生

那虚幻引擎是如何实现这个机制的呢?一种方法是,一开始编译的时候,把表格都填好,放到一个文件处,要找某家人的时候再取出来,但是有一个问题就是,每编译一次的时候,函数地址会发生变化,所以直接存储函数地址这种方法不行。第二种方法就是,存储”进行查找户口调查的过程”,比方说,它存储了每个家庭哪些人需要登记信息。然后当运行开始的时候,逐个让每一家人进行登记。而UHT就是在为这个过程提供帮助,它生成的.generate.h 和 .generate.cpp就是存储进行查找户口调查的过程。

2.2 .generate.h 和 .generate.cpp
“.generate.h”与“.generate.cpp”文件里面都是什么?有什么作用?

“.generate.h”里面是宏,而且包含一个非常庞大的宏,这个宏把所有和反射相关的方法(包括定义)和结构体连接到一起。

而“.generate.cpp”里面是许多的函数定义,UHT根据你在头文件里面使用的宏(UFUNCTION等)自动的生成这个文件,所以这个文件并不需要你去修改,也不允许修改。UBT属性通过扫描头文件,记录任何至少有一个反射类型的头文件的模块。如果其中任意一个头文件从上一次编译起发生了变化,那么 UHT就会被调用来利用和更新反射数据。UHT分析头文件,创建一系列反射数据,并且生成包含反射数据的C++代码。

2.3 反射类型和层次结构
官方文档所给出的基本层次结构

UFieldUStructUClass (C++ class)UScriptStruct (C++ struct)UFunction (C++ function)UEnum (C++ enumeration)UProperty (C++ member variable or function parameter)(Many subclasses for different types)

下图引自InsideUE4

2.4 热重载
​ 在编辑器模式下,UE4将工程代码编译成动态链接库,这样编辑器可以动态的加载和卸载某个动态链接库。UE4为工程自动生成一个cpp文件(本工程为HelloUE4.generated.cpp),cpp文件包含了当前工程中所有需要反射的类信息,以及类成员列表和每个成员的类型信息。在动态链接库被编辑器加载的时候,自动将类信息注册到编辑器中。反之,卸载的时候,这样类信息也会被反注册。
​ 在开发的过程中,当我们编译完成工程的时候,UE4编辑器会自动检测动态链接库的变化,然后自动热重载这些动态链接库中的类信息。

3. 反射代码实例分析

UE4引擎启动时候,是分Module(dll)来构建类型信息的。Module采用模拟一般程序的构建流程的方法,大致需要以下几个阶段:生成、收集、注册、链接。

生成阶段:借助UHT(Unreal Header Tool)工具,生成UClass代码,包括UClass构造,注册属性和函数等;
收集阶段:利用Static自动注册方式,在模块加载的时候,将所有UClass登记,集中在Array管理
注册阶段:在模块初始化的时候,将Array中的所有UClass相关的Function和Property注册;
链接阶段:就是反射功能。

生成阶段

要让一个类支持反射,你需要让这个类要继承自UObject、在类声明前添加UCLASS(或USTRUCT)标识,并且include “xxx.generated.h”头文件(而且必须是最后一个include)。

当你启动UE4编译时,UE4会首先运行UHT (UnrealHeaderTool),UHT成功运行后才会执行真正的编译。UHT是一个头文件解析和代码生成工具,它会处理所有的头文件,从中检索UCLASS、GENERATED_BODY、UPROPERTY、UFUNTION等关键字,检索到以后就为它们生成.generate.h 和 .generate.cpp。

以下是创建的一个小小的demo来对反射进行更好的理解

创建一个新的项目,然后创建一个类继承AGamoModeBase。

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "HelloGameMode.generated.h"     // 核心内容,必须放在最后一行,由UBT自动生成。UCLASS()
class HELLOWORLD_API AHelloGameMode : public AGameModeBase
{GENERATED_BODY()                  // 重中之重
protected:UPROPERTY(BlueprintReadWrite, Category = "AReflectionStudyGameMode")float Score;UFUNCTION(BlueprintCallable, Category = "AReflectionStudyGameMode")void CallableFuncTest();UFUNCTION(BlueprintNativeEvent, Category = "AReflectionStudyGameMode")void NavtiveFuncTest();UFUNCTION(BlueprintImplementableEvent, Category = "AReflectionStudyGameMode")void ImplementableFuncTest();};

首先UHT帮我自动生成了四个文件。

HelloGameMode.generated.h
UHT分析生成的文件内容如下:由于篇幅原因,只列出一部分代码。

 // ...// ...
#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_RPC_WRAPPERS_NO_PURE_DECLS \virtual void NavtiveFuncTest_Implementation(); \\DECLARE_FUNCTION(execNavtiveFuncTest) \{ \P_FINISH; \P_NATIVE_BEGIN; \P_THIS->NavtiveFuncTest_Implementation(); \P_NATIVE_END; \} \\DECLARE_FUNCTION(execCallableFuncTest) \{ \P_FINISH; \P_NATIVE_BEGIN; \P_THIS->CallableFuncTest(); \P_NATIVE_END; \}#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_EVENT_PARMS
#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_CALLBACK_WRAPPERS
#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_INCLASS_NO_PURE_DECLS \
private: \static void StaticRegisterNativesAHelloGameMode(); \friend struct Z_Construct_UClass_AHelloGameMode_Statics; \
public: \DECLARE_CLASS(AHelloGameMode, AGameModeBase, COMPILED_IN_FLAGS(0 | CLASS_Transient), CASTCLASS_None, TEXT("/Script/helloworld"), NO_API) \DECLARE_SERIALIZER(AHelloGameMode)#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_ENHANCED_CONSTRUCTORS \/** Standard constructor, called after all reflected properties have been initialized */ \NO_API AHelloGameMode(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \
private: \/** Private move- and copy-constructors, should never be used */ \NO_API AHelloGameMode(AHelloGameMode&&); \NO_API AHelloGameMode(const AHelloGameMode&); \
public: \DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, AHelloGameMode); \
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(AHelloGameMode); \DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode)#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_PRIVATE_PROPERTY_OFFSET \FORCEINLINE static uint32 __PPO__Score() { return STRUCT_OFFSET(AHelloGameMode, Score); }#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_GENERATED_BODY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \helloworld_Source_helloworld_Public_HelloGameMode_h_15_PRIVATE_PROPERTY_OFFSET \  // 关于UPROPERTY部分,具体还没研究透,看代码像是获取指针地址的偏移值helloworld_Source_helloworld_Public_HelloGameMode_h_15_RPC_WRAPPERS_NO_PURE_DECLS \  //helloworld_Source_helloworld_Public_HelloGameMode_h_15_CALLBACK_WRAPPERS \  // 空宏helloworld_Source_helloworld_Public_HelloGameMode_h_15_INCLASS_NO_PURE_DECLS \helloworld_Source_helloworld_Public_HelloGameMode_h_15_ENHANCED_CONSTRUCTORS \
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID helloworld_Source_helloworld_Public_HelloGameMode_hPRAGMA_ENABLE_DEPRECATION_WARNINGS

GENERATED_BODY

我们在HelloGameMode.cpp中发现,有一个GENERATED_BODY() 宏,该宏是重中之重,其他的UCLASS宏只是提供信息,不参与编译,而GENERATED_BODY正是把声明和元数据定义关联到一起的枢纽。继续查看宏定义。 GENERATED_BODY()的宏定义 :

#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY)

而在。generated.h Line63中定义了CURRENT_FILE_ID。所以GENERATED_BODY() 展开就是helloworld_Source_helloworld_Public_HelloGameMode_h_15_GENERATED_BODY,而这个宏的定义则在generated.h Line51。

DECLARE_FUNCTION
通过helloworld_Source_helloworld_Public_HelloGameMode_h_15_GENERATED_BODY向上, 以helloworld_Source_helloworld_Public_HelloGameMode_h_15_RPC_WRAPPERS_NO_PURE_DECLS为例:

#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_INCLASS_NO_PURE_DECLS \
private: \static void StaticRegisterNativesAHelloGameMode(); \    // 在HelloGameMode.gen.cpp中实现friend struct Z_Construct_UClass_AHelloGameMode_Statics; \ // 在HelloGameMode.gen.cpp中定义
public: \DECLARE_CLASS(AHelloGameMode, AGameModeBase, COMPILED_IN_FLAGS(0 | CLASS_Transient), CASTCLASS_None, TEXT("/Script/helloworld"), NO_API) \DECLARE_SERIALIZER(AHelloGameMode)      // 序列化,先忽略

DECLARE_CLASS是最重要的一个声明,对照着定义:DECLARE_CLASS(AHelloGameMode, AGameModeBase, COMPILED_IN_FLAGS(0 | CLASS_Transient), CASTCLASS_None, TEXT("/Script/helloworld"), NO_API)
TClass:类名
TSuperClass:基类名字
TStaticFlags:类的属性标记
TStaticCastFlags:指定了该类可以转换为哪些类,这里为0表示不能转为那些默认的类,读者可以自己查看EClassCastFlags声明来查看具体有哪些默认类转换。
TPackage:类所处于的包名,所有的对象都必须处于一个包中,而每个包都具有一个名字,可以通过该名字来查找。这里是”/Script/helloworld”,指定是Script下的helloworld,Script可以理解为用户自己的实现,不管是C++还是蓝图,都可以看作是引擎外的一种脚本,当然用这个名字也肯定有UE3时代UnrealScript的影子。Hello就是项目名字,该项目下定义的对象处于该包中。Package的概念涉及到后续Object的组织方式,目前可以简单理解为一个大的Object包含了其他子Object。
TRequiredAPI:就是用来Dll导入导出的标记,这里是NO_API1,因为是最终exe,不需要导出。

DefaultConstructor默认构造
helloworld_Source_helloworld_Public_HelloGameMode_h_15_ENHANCED_CONSTRUCTORS 部分:

#define helloworld_Source_helloworld_Public_HelloGameMode_h_15_ENHANCED_CONSTRUCTORS \/** Standard constructor, called after all reflected properties have been initialized */ \NO_API AHelloGameMode(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \   //根据注释的意思,就是标准的构造函数,在所有反射的属性初始化后调用。
private: \/** Private move- and copy-constructors, should never be used */ \     //永远不要用这两个函数NO_API AHelloGameMode(AHelloGameMode&&); \   //移动构造函数NO_API AHelloGameMode(const AHelloGameMode&); \   //拷贝构造函数
public: \DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, AHelloGameMode); \   // 热加载,先忽略
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(AHelloGameMode); \    // 空宏DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode)   // 定义一个构造函数

继续查看DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL的定义:

#define DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(TClass) \static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }声明定义了一个构造函数包装器。需要这么做的原因是,在根据名字反射创建对象的时候,需要调用该类的构造函数。可是类的构造函数并不能用函数指针指向,因此这里就用一个static函数包装一下,变成一个”平凡”的函数指针而且所有类的签名一致,就可以在UClass里用一个函数指针里保存起来 。

HelloGameMode.gen.cpp
由于这个类代码太长,所以的话就分版块讨论。

ProcessEvent

static FName NAME_AHelloGameMode_ImplementableFuncTest = FName(TEXT("ImplementableFuncTest"));
void AHelloGameMode::ImplementableFuncTest()
{ProcessEvent(FindFunctionChecked(NAME_AHelloGameMode_ImplementableFuncTest),NULL);
}
为啥BlueprintImplementableEvent的函数不用我们去实现呢,是因为UBT帮我们自己实现了。
而关于ProcessEvent部分, 这个方法在UObject中实现的。

刚接触UE4的时候,如果是BlueprintImplementabeEvent的函数,是不是发现不需要自己去实现,那么当时有没有觉得怪异呢,上面的代码就解释清楚了,那是UE4帮我们实现了,可以看到它调用了ProcessEvent方法,这个方法在UObject中实现的。
而且如果是BlueprintImplementabeEvent或者RPC的那些函数,我们是不需要实现其函数方法的,如果在CPP文件定义实现的话,则会报错,那是因为在.gen.cpp中已经帮我们实现了这个函数。

收集阶段

自动化注册
思考:UE4如何实现自动注册的呢?
主要是通过Static 自动注册的方式。
而在本例子是如何通过Static自动注册呢?
回顾生成阶段的IMPLEMENT_CLASS和ConstructClass中静态声明了两个变量。

#define IMPLEMENT_CLASS(TClass, TClassCrc) \static TClassCompiledInDefer<TClass> AutoInitialize##TClass(TEXT(#TClass), sizeof(TClass), TClassCrc); \

或者

static FCompiledInDefer Z_CompiledInDefer_UClass_AHelloGameMode(Z_Construct_UClass_AHelloGameMode, &AHelloGameMode::StaticClass, TEXT("/Script/helloworld"), TEXT("AHelloGameMode"), false, nullptr, nullptr, nullptr);

这两个全局静态变量在Main函数之前就会初始化

初始化就会调用这两个变量的构造函数

构造函数会分别调用UClassCompiledInDefer函数和UObjectCompiledInDefer函数。

这两个函数会把ClassInfo放进延迟注册的数组中去。

思考:为何需要TClassCompiledInDefer和FCompiledInDefer两个静态初始化来登记?

我们也观察到了这二者是一一对应的,问题是为何需要两个静态对象来分别收集,为何不合二为一?关键在于我们首先要明白它们二者的不同之处,前者的目的主要是为后续提供一个TClass::StaticClass的Register方法(其会触发GetPrivateStaticClassBody的调用,进而创建出UClass对象),而后者的目的是在其UClass身上继续调用构造函数,初始化属性和函数等一些注册操作。我们可以简单理解为就像是C++中new对象的两个步骤,首先分配内存,继而在该内存上构造对象。我们在后续的注册章节里还会继续讨论到这个问题。

思考:为啥要采用延迟注册的方式?为什么不在收集阶段直接注册呢?

主要考虑到UE4超多类,都在static初始化阶段注册,程序会表现启动速度慢,用户双击程序,没反应。所以采用延迟注册。

启动过程分析

上面我们讲解了这个注册信息的过程,而它们的执行是伴随着当前模块的加载而执行的,我们都知道静态变量的初始化是先于Main函数执行的。下面我们简单画了一下虚幻编辑器的启动流程,这样我们就可以准确地看到整个注册反射信息的过程了。

void ProcessNewlyLoadedUObjects()
{// ...UClassRegisterAllCompiledInClasses();const TArray<UClass* (*)()>& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration();const TArray<FPendingStructRegistrant>& DeferredCompiledInStructRegistration = GetDeferredCompiledInStructRegistration();const TArray<FPendingEnumRegistrant>& DeferredCompiledInEnumRegistration = GetDeferredCompiledInEnumRegistration();bool bNewUObjects = false;while( GFirstPendingRegistrant || DeferredCompiledInRegistration.Num() || DeferredCompiledInStructRegistration.Num() || DeferredCompiledInEnumRegistration.Num() ){bNewUObjects = true;UObjectProcessRegistrants();UObjectLoadAllCompiledInStructs();UObjectLoadAllCompiledInDefaultProperties();}
// ...
}

在这个函数中,可以看到void ProcessNewlyLoadedUObjects()这个函数就是我们主要关注的函数,我们前面讲到的注册的信息,包括类、结构体以及枚举类型的反射信息都会在这里进行注册。

收集
在生成阶段中,我们生成了很多Class、Property、Function的信息。但是我们需要它们的信息收集整合到我们需要的数据结构里保存,以便下一阶段的使用。

通过UClassCompiledInDefer收集

深入UClassCompiledInDefer方法我们可以发现。

void UClassCompiledInDefer(FFieldCompiledInInfo* ClassInfo, const TCHAR* Name, SIZE_T ClassSize, uint32 Crc)
{// We will either create a new class or update the static class pointer of the existing oneGetDeferredClassRegistration().Add(ClassInfo);  //static TArray<FFieldCompiledInInfo*> DeferredClassRegistration;
}

可以看到UClassCompiledInDefer(this, InName, InClassSize, InCrc),传进去4个参数,主要是收集三个数据,为啥要传4个参数呢,是因为把自己的指针传进去,为了方便后续调用Register()方法。

实现的功能主要是在一个静态Array里添加信息记录。

这个数组会在后续注册Class的时候会被调用,然后调用Register(),实现注册功能。

/** Register all loaded classes */
void UClassRegisterAllCompiledInClasses()
{//...TArray<FFieldCompiledInInfo*>& DeferredClassRegistration = GetDeferredClassRegistration();for (const FFieldCompiledInInfo* Class : DeferredClassRegistration){UClass* RegisteredClass = Class->Register();     //实现注册,关键步骤。
#if WITH_HOT_RELOADif (GIsHotReload && Class->OldClass == nullptr){AddedClasses.Add(RegisteredClass);}
#endif}DeferredClassRegistration.Empty();   //注册完成,清空注册表//...

而Register函数实际上传进来的就是StaticClass函数

接着就是StaticClass()部分如何收集的解释。

通过StaticClass()我们可以发现主要是调用了GetPrivateStaticClass(),而GetPrivateStaticClass()也主要是调用了GetPrivateStaticClassBody()函数,那么通过这个函数收集到了哪些信息呢。

以下是主要的信息:

**PackageName:**StaticPackage()

Name:类名,+1去掉U、A、F前缀,+11去掉_Deprecated前缀

ReturnClass:输出引用,也就是收集信息后产生的UClass,PrivateStaticClass

void(*RegisterNativeFunc)(): StaticRegisterNativesAHelloGameMode(), 收集原生的函数。

InSize: 类的大小

InClassConstructor:构造函数,在DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode)实现。

InSuperClassFn:&TClass::Super::StaticClass, 父类的注册函数。

//  in DECLARE_CLASS
inline static UClass* StaticClass()
{return GetPrivateStaticClass();
}//  in IMPLEMENT_CLASS
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,     // StaticRegisterNativesAHelloGameMode,sizeof(TClass), (EClassFlags)TClass::StaticClassFlags, TClass::StaticClassCastFlags(), TClass::StaticConfigName(), (UClass::ClassConstructorType)InternalConstructor<TClass>,    //构造函数,在DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode) 实现(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>, &TClass::AddReferencedObjects, &TClass::Super::StaticClass, &TClass::WithinClass::StaticClass ); } return PrivateStaticClass;
}

这里主要是通过收集到的信息构造UClass。

主要有三部分:

首先给ReturnClass分配内存,然后初始化构造。
首先确保自己的父类已经注册了,设置SuperStruct和 在全局PendingRegistrantsMaps中添加自己身类,延迟注册。(在注册阶段会对这部分进行解释)
Register the class’s native functions,注册自身原生的函数、蓝图可执行的函数,执行 StaticRegisterNativesAHelloGameMode();

// 删减部分代码
void GetPrivateStaticClassBody(const TCHAR* PackageName,const TCHAR* Name,UClass*& ReturnClass,void(*RegisterNativeFunc)(),uint32 InSize,EClassFlags InClassFlags,EClassCastFlags InClassCastFlags,const TCHAR* InConfigName,UClass::ClassConstructorType InClassConstructor,UClass::ClassVTableHelperCtorCallerType InClassVTableHelperCtorCaller,UClass::ClassAddReferencedObjectsType InClassAddReferencedObjects,UClass::StaticClassFunctionType InSuperClassFn,UClass::StaticClassFunctionType InWithinClassFn,bool bIsDynamic /*= false*/)
{if (!bIsDynamic){ReturnClass = (UClass*)GUObjectAllocator.AllocateUObject(sizeof(UClass), alignof(UClass), true);   // 内存分配// 构造函数ReturnClass = ::new (ReturnClass)UClass(EC_StaticConstructor,Name,InSize,InClassFlags,InClassCastFlags,InConfigName,EObjectFlags(RF_Public | RF_Standalone | RF_Transient | RF_MarkAsNative | RF_MarkAsRootSet),InClassConstructor,InClassVTableHelperCtorCaller,InClassAddReferencedObjects);check(ReturnClass);}// 设置SuperStruct和 在全局PendingRegistrantsMaps中添加自己身类,后续注册UClass的时候会从Maps去除自身。InitializePrivateStaticClass(InSuperClassFn(),ReturnClass,InWithinClassFn(),PackageName,Name);// Register the class's native functions.// 注册自身原生的函数。蓝图可执行的函数// 执行 StaticRegisterNativesAHelloGameMode();RegisterNativeFunc();
}

通过UObjectCompiledInDefer收集

深入UObjectCompiledInDefer方法我们可以发现:

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();checkSlow(!DeferredCompiledInRegistration.Contains(InRegister));DeferredCompiledInRegistration.Add(InRegister);// ...
}

传进去一个InRegister函数指针到延迟注册数组中,也就是Z_Construct_UClass_AHelloGameMode函数。

然后在启动阶段会调用UObjectLoadAllCompiledInDefaultProperties函数,遍历所有的延迟注册数组,进行收集注册

static void UObjectLoadAllCompiledInDefaultProperties()
{static FName LongEnginePackageName(TEXT("/Script/Engine"));TArray<UClass *(*)()>& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration();const bool bHaveRegistrants = DeferredCompiledInRegistration.Num() != 0;if( bHaveRegistrants ){TArray<UClass*> NewClasses;TArray<UClass*> NewClassesInCoreUObject;TArray<UClass*> NewClassesInEngine;TArray<UClass* (*)()> PendingRegistrants = MoveTemp(DeferredCompiledInRegistration);for (UClass* (*Registrant)() : PendingRegistrants){UClass* Class = Registrant();      // 在这一步调用了Z_Construct_UClass_AHelloGameMode函数//...}// ...}
}

而Z_Construct_UClass_AHelloGameMode函数主要是调用了ConstructClass函数。
可以看到,Construct函数传进去两个参数,一个OuterClass,一个ClassParams。

OuterClass是我们注册后得到的输出对象。

而ClassParams是我们需要收集的数据。
以下让我们对ClassParams的数据进行一些分析,下面是一些重要的数据部分:

ClassNoRegisterFunc: StaticClass,作用在上面有具体讲述

DependencySingletonFuncArray: 所依赖的构建函数,确保自己所依赖部分都被构建成功。

FunctionLinkArray:需要反射的函数数组

PropertyArray: 属性数组

const UE4CodeGen_Private::FClassParams Z_Construct_UClass_AHelloGameMode_Statics::ClassParams = {&AHelloGameMode::StaticClass,DependentSingletons, ARRAY_COUNT(DependentSingletons),0x009002A8u,FuncInfo, ARRAY_COUNT(FuncInfo),Z_Construct_UClass_AHelloGameMode_Statics::PropPointers, ARRAY_COUNT(Z_Construct_UClass_AHelloGameMode_Statics::PropPointers),nullptr,&StaticCppClassTypeInfo,nullptr, 0,METADATA_PARAMS(Z_Construct_UClass_AHelloGameMode_Statics::Class_MetaDataParams, ARRAY_COUNT(Z_Construct_UClass_AHelloGameMode_Statics::Class_MetaDataParams))
};struct FClassParams
{UClass*                                   (*ClassNoRegisterFunc)();      //  传进来StaticClass的函数UObject*                           (*const *DependencySingletonFuncArray)();   // 传进所依赖函数的函数数组int32                                       NumDependencySingletons;  //上述函数的个数uint32                                      ClassFlags; // EClassFlagsconst FClassFunctionLinkInfo*               FunctionLinkArray;    // 需要反射的函数,具体的后面会讲述int32                                       NumFunctions;  //同上const FPropertyParamsBase* const*           PropertyArray;   // 后续再讲这个int32                                       NumProperties; //同上const char*                                 ClassConfigNameUTF8;const FCppClassTypeInfoStatic*              CppClassInfo;const FImplementedInterfaceParams*          ImplementedInterfaceArray;   //本例子中没用上int32                                       NumImplementedInterfaces;   // 0
#if WITH_METADATAconst FMetaDataPairParam*                   MetaDataArray;int32                                       NumMetaData;
#endif
};

以下是ConstructUclass的部分内容。

根据流程图,可以看到,首先是执行的DependecySingletonFunctions,检测自己所依赖的单例是否都建立了反射关系。接着就是StaticClass函数部分,这也是我们重点关注的部分。在StaticClass,实现了构建了一个基础的UClass的反射,但是还没有完善细节,因此在后续部分给NewClass添加细节部分。

步骤:

执行DependecySingletonFunctions数组中所依赖的函数,确保自身的父类反射已经被构建和自身所在的Package反射被构建。
UObjectForceRegistration,把自己从等待注册的Map中去掉, 并且注册。
ConstructUFunction,组建Function,并且注册Function。(这部分后续再探讨
ConstructUProperties,组建Properties。(这部分后续再探讨

void ConstructUClass(UClass*& OutClass, const FClassParams& Params)  // 关键点,用了引用
{if (OutClass && (OutClass->ClassFlags & CLASS_Constructed))  // 如果已经构建过了,则返回{return;}// 执行所依赖过的函数, 需要所依赖部分已经建立反射关系。(PS: Ue4源码部分 充斥着大量的懒汉单例模式)for (UObject* (*const *SingletonFunc)() = Params.DependencySingletonFuncArray, *(*const *SingletonFuncEnd)() = SingletonFunc + Params.NumDependencySingletons; SingletonFunc != SingletonFuncEnd; ++SingletonFunc){(*SingletonFunc)();}// 所传进来的是StaticClass函数UClass* NewClass = Params.ClassNoRegisterFunc();   OutClass = NewClass;if (NewClass->ClassFlags & CLASS_Constructed)  // 如果这个已经构建过了,返回{return;}// 延迟注册 // 把自己从等待注册的Map中去掉UObjectForceRegistration(NewClass);    NewClass->ClassFlags |= (EClassFlags)(Params.ClassFlags | CLASS_Constructed);// Make sure the reference token stream is empty since it will be reconstructed later on// This should not apply to intrinsic classes since they emit native references before AssembleReferenceTokenStream is called.if ((NewClass->ClassFlags & CLASS_Intrinsic) != CLASS_Intrinsic){check((NewClass->ClassFlags & CLASS_TokenStreamAssembled) != CLASS_TokenStreamAssembled);NewClass->ReferenceTokenStream.Empty();
#if ENABLE_GC_OBJECT_CHECKSNewClass->DebugTokenMap.Empty();
#endif}// 创建Function的反射,利用传进来的构建函数来构建函数。NewClass->CreateLinkAndAddChildFunctionsToMap(Params.FunctionLinkArray, Params.NumFunctions);// 如果看过上一篇blog的话,就可以发现其实属性构建部分是没有单独的函数构建的// 原来在这部分实现。ConstructUProperties(NewClass, Params.PropertyArray, Params.NumProperties);// ConstructClass还有很多内容,但是由于篇幅关系,这里就不一一放出来。

注册阶段


注册阶段主要分为两个过程,首先构建UClass的时候添加进等待注册表,等待注册,在收集完成UClass所有信息的时候,延迟注册。

注册过程分析
Ue4是如何完成注册的呢?

/*** Convert a boot-strap registered class into a real one, add to uobject array, etc** @param UClassStaticClass Now that it is known, fill in UClass::StaticClass() as the class*/
void UObjectBase::DeferredRegister(UClass *UClassStaticClass,const TCHAR* PackageName,const TCHAR* InName)
{check(Internal::GObjInitialized);// Set object properties.UPackage* Package = CreatePackage(nullptr, PackageName);check(Package);Package->SetPackageFlags(PKG_CompiledIn);OuterPrivate = Package;check(UClassStaticClass);check(!ClassPrivate);ClassPrivate = UClassStaticClass;// Add to the global object table.AddObject(FName(InName), EInternalObjectFlags::None);// Make sure that objects disregarded for GC are part of root set.check(!GUObjectArray.IsDisregardForGC(this) || GUObjectArray.IndexToObject(InternalIndex)->IsRootSet());

其实就是把UClass放到一个Hash表中去。

void HashObject(UObjectBase* Object)
{auto& ThreadHash = FUObjectHashTables::Get();Hash = GetObjectHash(Name);                 ThreadHash.AddToHash(Hash, Object);Hash = GetObjectOuterHash( Name, (PTRINT)Object->GetOuter() );ThreadHash.HashOuter.Add( Hash, Object );AddToOuterMap( ThreadHash, Object );AddToClassMap( ThreadHash, Object );
}

通过上面的代码,我们可以发现通过名字生成Index和通过OuterName(也就是PackageName)来生成Index,然后添加进Map内部中。具体如何添加进HashMap中呢?请看下面的分析。

TUObjectHashTables
在这里就不得不介绍这个类TUObjectHashTables了,主要的存储的类。注册的反射的数据都以存放在这里。
首先看看这个类的定义。

class FUObjectHashTables
{FCriticalSection CriticalSection;public:/** Hash sets */TMap<int32, FHashBucket> Hash;TMultiMap<int32, class UObjectBase*> HashOuter;/** Map of object to their outers, used to avoid an object iterator to find such things. **/TMap<UObjectBase*, FHashBucket> ObjectOuterMap;TMap<UClass*, TSet<UObjectBase*> > ClassToObjectListMap;TMap<UClass*, TSet<UClass*> > ClassToChildListMap;static FUObjectHashTables& Get()   // 饿汉单例{static FUObjectHashTables Singleton;return Singleton;}FORCEINLINE void AddToHash(int32 InHash, UObjectBase* Object){FHashBucket& Bucket = Hash.FindOrAdd(InHash);Bucket.Add(Object);}
}

首先这个类用了饿汉单例的设计模式,保证只有一个存储数据的实例,

从这个类中我们可以看到有5个Map,具体的映射方式也都显而易见了,看看我们注册的时候用了这几个Map是怎样处理的。

void HashObject(UObjectBase* Object)
{auto& ThreadHash = FUObjectHashTables::Get();    //获取存储单例Hash = GetObjectHash(Name);     //把FName转化为Int Index,来映射            ThreadHash.AddToHash(Hash, Object);Hash = GetObjectOuterHash( Name, (PTRINT)Object->GetOuter() );ThreadHash.HashOuter.Add( Hash, Object );AddToOuterMap( ThreadHash, Object );AddToClassMap( ThreadHash, Object );
}

第一个Map:Hash,TMap<int32, FHashBucket> Hash,映射方式通过int32映射到一个桶。(每个桶我们其实都可以看成是一个类的集合)(为了容易理解,此处的int32亦可以理解为FName)

ThreadHash.AddToHash(Hash, Object);FORCEINLINE void AddToHash(int32 InHash, UObjectBase* Object)
{FHashBucket& Bucket = Hash.FindOrAdd(InHash);Bucket.Add(Object);
}

通过代码可以看出来,通过自己的名字找到了属于自己的那个桶,并且在桶中把自己添加进去。

由此可以看出HashMap的用处其实就是通过自身名字找到属于自己的桶集合。

第二个Map:HashOuter,TMultiMap<int32, class UObjectBase*> HashOuter,映射方式通过int32映射到一个UObjectBase类。(为了容易理解,此处的int32亦可以理解为OuterFName)

ThreadHash.HashOuter.Add( Hash, Object );

通过代码可以看出,HashOuterMap的用处其实是通过OuterFName的名字来找到自己的Outer。

第三个Map:ObjectOuterMap,TMap<UObjectBase*, FHashBucket> ObjectOuterMap,映射方式是用过Outer来找到自己的Bucket。

AddToOuterMap( ThreadHash, Object );// Assumes that ThreadHash's critical is already locked
FORCEINLINE static void AddToOuterMap(FUObjectHashTables& ThreadHash, UObjectBase* Object)
{FHashBucket& Bucket = ThreadHash.ObjectOuterMap.FindOrAdd(Object->GetOuter());checkSlow(!Bucket.Contains(Object)); // if it already exists, something is wrong with the external codeBucket.Add(Object);
}

通过代码可以看出, ObjectOuterMap是通过Outer来获取到Outer的Bucket。(Outer实际上就是Package)(类似于第二个Map)

第四个Map:ClassToObjectListMap,TMap<UClass*, TSet<UObjectBase*> > ClassToObjectListMap。

{check(Object->GetClass());TSet<UObjectBase*>& ObjectList = ThreadHash.ClassToObjectListMap.FindOrAdd(Object->GetClass());bool bIsAlreadyInSetPtr = false;ObjectList.Add(Object, &bIsAlreadyInSetPtr);check(!bIsAlreadyInSetPtr); // if it already exists, something is wrong with the external code
}

通过代码可以看出, ClassToObjectListMap是通过自身来获取到自身所包含的一个Set(类似于第一个Map)。

第五个Map:ClassToChildListMap,TMap<UClass*, TSet<UClass*> > ClassToChildListMap

UObjectBaseUtility* ObjectWithUtility = static_cast<UObjectBaseUtility*>(Object);
if ( ObjectWithUtility->IsA(UClass::StaticClass()) )
{UClass* Class = static_cast<UClass*>(ObjectWithUtility);UClass* SuperClass = Class->GetSuperClass();if ( SuperClass ){TSet<UClass*>& ChildList = ThreadHash.ClassToChildListMap.FindOrAdd(SuperClass);bool bIsAlreadyInSetPtr = false;ChildList.Add(Class, &bIsAlreadyInSetPtr);check(!bIsAlreadyInSetPtr); // if it already exists, something is wrong with the external code}
}

UE4反射原理(转)相关推荐

  1. UE4反射原理的探究

    UE4反射 本文主要是个人对UE4反射系统的一些总结和理解. 1. UE4反射系统 什么是反射系统 在UE4里面,你无时无刻都会看到类似UFUNCTION()这样的宏.官方文档告诉你,只要在一个函数的 ...

  2. ue4 4.24启动无响应_UE4项目问题集合

    debugeditor模式下,在LoadPackage过程中crash,vs报出Stack overflow的错误 这是由于Package层数过多,vs栈不够用导致的.而ue4用UBT导致不能直接修改 ...

  3. ue4 曲线图实现 蓝图_UE4蓝图解析(一)

    简介 蓝图是一类特殊的asset,可以使用直观.基于节点的方式创建逻辑,或者设置一些变量数据.策划可以创建自定义的Actor.Event.函数等等,快速的做Gameplay迭代,不需要写任何代码. 蓝 ...

  4. UE4材质着色器全面学习教程

    你会学到什么 通过所有着色器类型和设计的实际演示,学习创建材质 要求 对虚幻的基本理解会有所帮助 了解纹理的一般知识(不仅限于UE4)也很有用 描述 在这个系列中,我将带你设置大量不同的材料,教你如何 ...

  5. UE4制作程序背景游戏 Make a game with Procedural Backgrounds in UE4

    使用虚幻引擎4蓝图创建一个程序背景的游戏 你会学到什么 学习虚幻引擎4要领 使用程序切片创建标高 保存并加载某些游戏元素 创造一个无止境的跑步者角色 创建和完成游戏的良好习惯和实践 MP4 |视频:h ...

  6. UE4蓝图无代码编程游戏开发技能学习教程

    在虚幻引擎4中创建.设计和开发自己的游戏,无需编码 你会学到什么 虚幻引擎4中使用蓝图的游戏开发(无代码编程) 使用行业标准方法的游戏设计 使用Maya进行三维设计 在本课程中创建您的第一个游戏 Ga ...

  7. UE4风格化场景设计入门指南 Stylized Station – The Environment Artist’s Survival Kit

    持续时间13h 1920X1080 .ts 包含项目文件 大小解压后:4.9G 语言:英语+中文字幕(人工校对) 标题:风格化的车站--环境艺术家的生存工具包 信息: 环境艺术很难. 尤其是作为初学者 ...

  8. 在UE4中创建CG动画 How to create a movie in Unreal Engine 4 using Metahuman

    MP4 |视频:h264,1280×720 |音频:AAC,44.1 KHz 语言:英语+中英文字幕(根据原英文字幕机译更准确)|大小解压后:1.55 GB |时长:1h 16m 你会学到什么 如何在 ...

  9. UE4创建第一人称射击游戏学习教程

    Unreal Engine 4: Create Your Own First-Person Shooter MP4 |视频:h264,1280×720 |音频:AAC,44.1 KHz,2 Ch 语言 ...

最新文章

  1. 极速理解设计模式系列:11.单例模式(Singleton Pattern)
  2. 【通俗理解线性代数】 -- 理解二次型
  3. linux grep find查找文件夹、代码中的某行/字符串
  4. Win64 驱动内核编程-19.HOOK-SSDT
  5. [转] WinForm实现移除控件某个事件的方法
  6. 1.6 this关键字详解(3种用法)
  7. VMware workstation 8.0上安装VMware ESXI5.0
  8. MyBatis复习(九):MyBatis整合C3P0连接池
  9. STM32的EXTI相关学习笔记
  10. Python批量提取Word文件题库中的答案
  11. 接触线叉环插座行业调研报告 - 市场现状分析与发展前景预测
  12. mysql常用的yu语句_mysql常用sql语句
  13. 《程序设计技术》第八章例程
  14. Atitit.ALT+TAB没反应车and 点击任务栏程序闪烁但是不能切换
  15. python vba sql_Excel、VBA与Mysql交互
  16. 永磁同步电机矢量控制(四)——simulink仿真搭建
  17. oracle数据库查看建表语句,oracle 查看建表语句
  18. Codeforces 1006A
  19. 手把手教你如何向 Linux 内核提交代码
  20. 曙光服务器显示器接入只显示logo,显示屏只显示显示屏品牌logo,没有其他反应

热门文章

  1. 儿童php钢板设计原理,儿童股骨干中段骨折:弹性髓内钉优于钢板固定
  2. matlab 实物仿真平台,AppSIM 实时半实物仿真平台
  3. 模型5.4——对数据进行正态分布检验
  4. 4.DQL查询数据(超重点)
  5. 饿了么外卖商家网页IM通信协议
  6. 有名管道和无名管道的介绍
  7. 代码评审平台 phabricator-docker/phabricator docker 服务搭建及配置
  8. 网络渗透测试学习一之网络扫描与网络侦察
  9. SpringMVC---四大作用域
  10. matlab画折现_MATLAB实例:绘制折线图