UE4反射原理(转)
轉(zhuǎn)自:
https://blog.csdn.net/mohuak/article/details/81913532
https://blog.csdn.net/u012999985/article/details/52902065
1. UE4反射系統(tǒng)
什么是反射系統(tǒng)
在UE4里面,你無時無刻都會看到類似UFUNCTION()這樣的宏。官方文檔告訴你,只要在一個函數(shù)的前面加上這個宏,然后在括號里面加上BlueprintCallable就可以在編輯器里面調(diào)用了。按照他的指示,我們就能讓我們的函數(shù)實現(xiàn)各種各樣特別的功能,那這個效果就是通過UE4的反射系統(tǒng)來實現(xiàn)的。
其實,所謂反射,是程序在運行時進行自檢的一種能力,自檢什么呢?我認(rèn)為就是檢查自己的C++類,函數(shù),成員變量,結(jié)構(gòu)體等等(對應(yīng)起來也就是大家在UE4能看到的UCLASS,UFUNCTON,UPROPERTY,USTRUCT后面還會提到)。
那檢查這些東西做什么呢?最明顯的就是支持藍圖和C++的交互功能,說的更通俗一點,就是可以更自由的控制這些結(jié)構(gòu),讓他在我們想出現(xiàn)的地方出現(xiàn),讓他在我們想使用的地方使用。要知道我們在虛幻4中聲明的任意一個類,都是繼承于UObject類的,所以他遠遠不是我們所以為的那個普通的C++類。我們可以使用這個類進行網(wǎng)絡(luò)復(fù)制,執(zhí)行垃圾回收,讓他和藍圖交互等等。而這一切原生的C++是并不支持的,也正是因此虛幻4才構(gòu)建了一個這樣的反射系統(tǒng)。
2. 反射實現(xiàn)機制和基本原理
在了解反射系統(tǒng)前,我們必須要知道兩個UE4獨特的文件類型—“.generate.h”以及“.generate.cpp”。“.generate.h”文件在每一個類的聲明前面都會被包含到對應(yīng)的頭文件里面。(這也是官方建議我們要用編輯器來創(chuàng)建類的原因,他們并不是常規(guī)的C++類)而“.generate.cpp”對于一整個項目只會有一個。這兩種文件可以說是反射系統(tǒng)的關(guān)鍵所在,他們是通過Unreal Build Tool(UBT) 和UnrealHeaderTool(UHT)來生成的。
2.1 UBT 和UHT
UnrealBuildTool(UBT,C#):UE4的自定義工具,來編譯UE4的逐個模塊并處理依賴等。我們編寫的Target.cs,Build.cs都是為這個工具服務(wù)的。
UnrealHeaderTool (UHT,C++):UE4的C++代碼解析生成工具,我們在代碼里寫的那些宏UCLASS等和#include “*.generated.h”都為UHT提供了信息來生成相應(yīng)的C++反射代碼。
代碼編譯在兩個階段中進行:1.UHT 被調(diào)用。它將解析 C++ 頭中引擎相關(guān)類元數(shù)據(jù),并生成自定義代碼,以實現(xiàn)諸多 UObject 相關(guān)的功能。2.普通 C++ 編譯器被調(diào)用,以便對結(jié)果進行編譯。)
思考:UHT如何實現(xiàn)反射?
打個比方:UClass其實就好比一張表,一張戶口本的東西,指向”真實家庭“的指針。上面記錄著一些信息,好比:
張三:
男
1995年出生
李四:
女
1991年出生
那虛幻引擎是如何實現(xiàn)這個機制的呢?一種方法是,一開始編譯的時候,把表格都填好,放到一個文件處,要找某家人的時候再取出來,但是有一個問題就是,每編譯一次的時候,函數(shù)地址會發(fā)生變化,所以直接存儲函數(shù)地址這種方法不行。第二種方法就是,存儲”進行查找戶口調(diào)查的過程”,比方說,它存儲了每個家庭哪些人需要登記信息。然后當(dāng)運行開始的時候,逐個讓每一家人進行登記。而UHT就是在為這個過程提供幫助,它生成的.generate.h 和 .generate.cpp就是存儲進行查找戶口調(diào)查的過程。
2.2 .generate.h 和 .generate.cpp
“.generate.h”與“.generate.cpp”文件里面都是什么?有什么作用?
“.generate.h”里面是宏,而且包含一個非常龐大的宏,這個宏把所有和反射相關(guān)的方法(包括定義)和結(jié)構(gòu)體連接到一起。
而“.generate.cpp”里面是許多的函數(shù)定義,UHT根據(jù)你在頭文件里面使用的宏(UFUNCTION等)自動的生成這個文件,所以這個文件并不需要你去修改,也不允許修改。UBT屬性通過掃描頭文件,記錄任何至少有一個反射類型的頭文件的模塊。如果其中任意一個頭文件從上一次編譯起發(fā)生了變化,那么 UHT就會被調(diào)用來利用和更新反射數(shù)據(jù)。UHT分析頭文件,創(chuàng)建一系列反射數(shù)據(jù),并且生成包含反射數(shù)據(jù)的C++代碼。
2.3 反射類型和層次結(jié)構(gòu)
官方文檔所給出的基本層次結(jié)構(gòu)
下圖引自InsideUE4
2.4 熱重載
? 在編輯器模式下,UE4將工程代碼編譯成動態(tài)鏈接庫,這樣編輯器可以動態(tài)的加載和卸載某個動態(tài)鏈接庫。UE4為工程自動生成一個cpp文件(本工程為HelloUE4.generated.cpp),cpp文件包含了當(dāng)前工程中所有需要反射的類信息,以及類成員列表和每個成員的類型信息。在動態(tài)鏈接庫被編輯器加載的時候,自動將類信息注冊到編輯器中。反之,卸載的時候,這樣類信息也會被反注冊。
? 在開發(fā)的過程中,當(dāng)我們編譯完成工程的時候,UE4編輯器會自動檢測動態(tài)鏈接庫的變化,然后自動熱重載這些動態(tài)鏈接庫中的類信息。
3. 反射代碼實例分析
UE4引擎啟動時候,是分Module(dll)來構(gòu)建類型信息的。Module采用模擬一般程序的構(gòu)建流程的方法,大致需要以下幾個階段:生成、收集、注冊、鏈接。
生成階段:借助UHT(Unreal Header Tool)工具,生成UClass代碼,包括UClass構(gòu)造,注冊屬性和函數(shù)等;
收集階段:利用Static自動注冊方式,在模塊加載的時候,將所有UClass登記,集中在Array管理
注冊階段:在模塊初始化的時候,將Array中的所有UClass相關(guān)的Function和Property注冊;
鏈接階段:就是反射功能。
生成階段
要讓一個類支持反射,你需要讓這個類要繼承自UObject、在類聲明前添加UCLASS(或USTRUCT)標(biāo)識,并且include “xxx.generated.h”頭文件(而且必須是最后一個include)。
當(dāng)你啟動UE4編譯時,UE4會首先運行UHT (UnrealHeaderTool),UHT成功運行后才會執(zhí)行真正的編譯。UHT是一個頭文件解析和代碼生成工具,它會處理所有的頭文件,從中檢索UCLASS、GENERATED_BODY、UPROPERTY、UFUNTION等關(guān)鍵字,檢索到以后就為它們生成.generate.h 和 .generate.cpp。
以下是創(chuàng)建的一個小小的demo來對反射進行更好的理解
創(chuàng)建一個新的項目,然后創(chuàng)建一個類繼承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" // 核心內(nèi)容,必須放在最后一行,由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分析生成的文件內(nèi)容如下:由于篇幅原因,只列出一部分代碼。
GENERATED_BODY
我們在HelloGameMode.cpp中發(fā)現(xiàn),有一個GENERATED_BODY() 宏,該宏是重中之重,其他的UCLASS宏只是提供信息,不參與編譯,而GENERATED_BODY正是把聲明和元數(shù)據(jù)定義關(guān)聯(lián)到一起的樞紐。繼續(xù)查看宏定義。 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為例:
DECLARE_CLASS是最重要的一個聲明,對照著定義:DECLARE_CLASS(AHelloGameMode, AGameModeBase, COMPILED_IN_FLAGS(0 | CLASS_Transient), CASTCLASS_None, TEXT("/Script/helloworld"), NO_API)
TClass:類名
TSuperClass:基類名字
TStaticFlags:類的屬性標(biāo)記
TStaticCastFlags:指定了該類可以轉(zhuǎn)換為哪些類,這里為0表示不能轉(zhuǎn)為那些默認(rèn)的類,讀者可以自己查看EClassCastFlags聲明來查看具體有哪些默認(rèn)類轉(zhuǎn)換。
TPackage:類所處于的包名,所有的對象都必須處于一個包中,而每個包都具有一個名字,可以通過該名字來查找。這里是”/Script/helloworld”,指定是Script下的helloworld,Script可以理解為用戶自己的實現(xiàn),不管是C++還是藍圖,都可以看作是引擎外的一種腳本,當(dāng)然用這個名字也肯定有UE3時代UnrealScript的影子。Hello就是項目名字,該項目下定義的對象處于該包中。Package的概念涉及到后續(xù)Object的組織方式,目前可以簡單理解為一個大的Object包含了其他子Object。
TRequiredAPI:就是用來Dll導(dǎo)入導(dǎo)出的標(biāo)記,這里是NO_API1,因為是最終exe,不需要導(dǎo)出。
DefaultConstructor默認(rèn)構(gòu)造
helloworld_Source_helloworld_Public_HelloGameMode_h_15_ENHANCED_CONSTRUCTORS 部分:
繼續(xù)查看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); }聲明定義了一個構(gòu)造函數(shù)包裝器。需要這么做的原因是,在根據(jù)名字反射創(chuàng)建對象的時候,需要調(diào)用該類的構(gòu)造函數(shù)。可是類的構(gòu)造函數(shù)并不能用函數(shù)指針指向,因此這里就用一個static函數(shù)包裝一下,變成一個”平凡”的函數(shù)指針而且所有類的簽名一致,就可以在UClass里用一個函數(shù)指針里保存起來 。HelloGameMode.gen.cpp
由于這個類代碼太長,所以的話就分版塊討論。
ProcessEvent
static FName NAME_AHelloGameMode_ImplementableFuncTest = FName(TEXT("ImplementableFuncTest")); void AHelloGameMode::ImplementableFuncTest() {ProcessEvent(FindFunctionChecked(NAME_AHelloGameMode_ImplementableFuncTest),NULL); } 為啥BlueprintImplementableEvent的函數(shù)不用我們?nèi)崿F(xiàn)呢,是因為UBT幫我們自己實現(xiàn)了。 而關(guān)于ProcessEvent部分, 這個方法在UObject中實現(xiàn)的。剛接觸UE4的時候,如果是BlueprintImplementabeEvent的函數(shù),是不是發(fā)現(xiàn)不需要自己去實現(xiàn),那么當(dāng)時有沒有覺得怪異呢,上面的代碼就解釋清楚了,那是UE4幫我們實現(xiàn)了,可以看到它調(diào)用了ProcessEvent方法,這個方法在UObject中實現(xiàn)的。
而且如果是BlueprintImplementabeEvent或者RPC的那些函數(shù),我們是不需要實現(xiàn)其函數(shù)方法的,如果在CPP文件定義實現(xiàn)的話,則會報錯,那是因為在.gen.cpp中已經(jīng)幫我們實現(xiàn)了這個函數(shù)。
收集階段
自動化注冊
思考:UE4如何實現(xiàn)自動注冊的呢?
主要是通過Static 自動注冊的方式。
而在本例子是如何通過Static自動注冊呢?
回顧生成階段的IMPLEMENT_CLASS和ConstructClass中靜態(tài)聲明了兩個變量。
或者
static FCompiledInDefer Z_CompiledInDefer_UClass_AHelloGameMode(Z_Construct_UClass_AHelloGameMode, &AHelloGameMode::StaticClass, TEXT("/Script/helloworld"), TEXT("AHelloGameMode"), false, nullptr, nullptr, nullptr);這兩個全局靜態(tài)變量在Main函數(shù)之前就會初始化
初始化就會調(diào)用這兩個變量的構(gòu)造函數(shù)
構(gòu)造函數(shù)會分別調(diào)用UClassCompiledInDefer函數(shù)和UObjectCompiledInDefer函數(shù)。
這兩個函數(shù)會把ClassInfo放進延遲注冊的數(shù)組中去。
思考:為何需要TClassCompiledInDefer和FCompiledInDefer兩個靜態(tài)初始化來登記?
我們也觀察到了這二者是一一對應(yīng)的,問題是為何需要兩個靜態(tài)對象來分別收集,為何不合二為一?關(guān)鍵在于我們首先要明白它們二者的不同之處,前者的目的主要是為后續(xù)提供一個TClass::StaticClass的Register方法(其會觸發(fā)GetPrivateStaticClassBody的調(diào)用,進而創(chuàng)建出UClass對象),而后者的目的是在其UClass身上繼續(xù)調(diào)用構(gòu)造函數(shù),初始化屬性和函數(shù)等一些注冊操作。我們可以簡單理解為就像是C++中new對象的兩個步驟,首先分配內(nèi)存,繼而在該內(nèi)存上構(gòu)造對象。我們在后續(xù)的注冊章節(jié)里還會繼續(xù)討論到這個問題。
思考:為啥要采用延遲注冊的方式?為什么不在收集階段直接注冊呢?
主要考慮到UE4超多類,都在static初始化階段注冊,程序會表現(xiàn)啟動速度慢,用戶雙擊程序,沒反應(yīng)。所以采用延遲注冊。
啟動過程分析
上面我們講解了這個注冊信息的過程,而它們的執(zhí)行是伴隨著當(dāng)前模塊的加載而執(zhí)行的,我們都知道靜態(tài)變量的初始化是先于Main函數(shù)執(zhí)行的。下面我們簡單畫了一下虛幻編輯器的啟動流程,這樣我們就可以準(zhǔn)確地看到整個注冊反射信息的過程了。
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();} // ... }在這個函數(shù)中,可以看到void ProcessNewlyLoadedUObjects()這個函數(shù)就是我們主要關(guān)注的函數(shù),我們前面講到的注冊的信息,包括類、結(jié)構(gòu)體以及枚舉類型的反射信息都會在這里進行注冊。
收集
在生成階段中,我們生成了很多Class、Property、Function的信息。但是我們需要它們的信息收集整合到我們需要的數(shù)據(jù)結(jié)構(gòu)里保存,以便下一階段的使用。
通過UClassCompiledInDefer收集
深入UClassCompiledInDefer方法我們可以發(fā)現(xiàn)。
可以看到UClassCompiledInDefer(this, InName, InClassSize, InCrc),傳進去4個參數(shù),主要是收集三個數(shù)據(jù),為啥要傳4個參數(shù)呢,是因為把自己的指針傳進去,為了方便后續(xù)調(diào)用Register()方法。
實現(xiàn)的功能主要是在一個靜態(tài)Array里添加信息記錄。
這個數(shù)組會在后續(xù)注冊Class的時候會被調(diào)用,然后調(diào)用Register(),實現(xiàn)注冊功能。
/** Register all loaded classes */ void UClassRegisterAllCompiledInClasses() {//...TArray<FFieldCompiledInInfo*>& DeferredClassRegistration = GetDeferredClassRegistration();for (const FFieldCompiledInInfo* Class : DeferredClassRegistration){UClass* RegisteredClass = Class->Register(); //實現(xiàn)注冊,關(guān)鍵步驟。 #if WITH_HOT_RELOADif (GIsHotReload && Class->OldClass == nullptr){AddedClasses.Add(RegisteredClass);} #endif}DeferredClassRegistration.Empty(); //注冊完成,清空注冊表//...而Register函數(shù)實際上傳進來的就是StaticClass函數(shù)
接著就是StaticClass()部分如何收集的解釋。
通過StaticClass()我們可以發(fā)現(xiàn)主要是調(diào)用了GetPrivateStaticClass(),而GetPrivateStaticClass()也主要是調(diào)用了GetPrivateStaticClassBody()函數(shù),那么通過這個函數(shù)收集到了哪些信息呢。
以下是主要的信息:
**PackageName:**StaticPackage()
Name:類名,+1去掉U、A、F前綴,+11去掉_Deprecated前綴
ReturnClass:輸出引用,也就是收集信息后產(chǎn)生的UClass,PrivateStaticClass
void(*RegisterNativeFunc)(): StaticRegisterNativesAHelloGameMode(), 收集原生的函數(shù)。
InSize: 類的大小
InClassConstructor:構(gòu)造函數(shù),在DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode)實現(xiàn)。
InSuperClassFn:&TClass::Super::StaticClass, 父類的注冊函數(shù)。
// 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 */// 主要實現(xiàn)內(nèi)容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>, //構(gòu)造函數(shù),在DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AHelloGameMode) 實現(xiàn)(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>, &TClass::AddReferencedObjects, &TClass::Super::StaticClass, &TClass::WithinClass::StaticClass ); } return PrivateStaticClass; }這里主要是通過收集到的信息構(gòu)造UClass。
主要有三部分:
首先給ReturnClass分配內(nèi)存,然后初始化構(gòu)造。
首先確保自己的父類已經(jīng)注冊了,設(shè)置SuperStruct和 在全局PendingRegistrantsMaps中添加自己身類,延遲注冊。(在注冊階段會對這部分進行解釋)
Register the class’s native functions,注冊自身原生的函數(shù)、藍圖可執(zhí)行的函數(shù),執(zhí)行 StaticRegisterNativesAHelloGameMode();
通過UObjectCompiledInDefer收集
深入UObjectCompiledInDefer方法我們可以發(fā)現(xiàn):
傳進去一個InRegister函數(shù)指針到延遲注冊數(shù)組中,也就是Z_Construct_UClass_AHelloGameMode函數(shù)。
然后在啟動階段會調(diào)用UObjectLoadAllCompiledInDefaultProperties函數(shù),遍歷所有的延遲注冊數(shù)組,進行收集注冊
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(); // 在這一步調(diào)用了Z_Construct_UClass_AHelloGameMode函數(shù)//...}// ...} }而Z_Construct_UClass_AHelloGameMode函數(shù)主要是調(diào)用了ConstructClass函數(shù)。
可以看到,Construct函數(shù)傳進去兩個參數(shù),一個OuterClass,一個ClassParams。
OuterClass是我們注冊后得到的輸出對象。
而ClassParams是我們需要收集的數(shù)據(jù)。
以下讓我們對ClassParams的數(shù)據(jù)進行一些分析,下面是一些重要的數(shù)據(jù)部分:
ClassNoRegisterFunc: StaticClass,作用在上面有具體講述
DependencySingletonFuncArray: 所依賴的構(gòu)建函數(shù),確保自己所依賴部分都被構(gòu)建成功。
FunctionLinkArray:需要反射的函數(shù)數(shù)組
PropertyArray: 屬性數(shù)組
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的函數(shù)UObject* (*const *DependencySingletonFuncArray)(); // 傳進所依賴函數(shù)的函數(shù)數(shù)組int32 NumDependencySingletons; //上述函數(shù)的個數(shù)uint32 ClassFlags; // EClassFlagsconst FClassFunctionLinkInfo* FunctionLinkArray; // 需要反射的函數(shù),具體的后面會講述int32 NumFunctions; //同上const FPropertyParamsBase* const* PropertyArray; // 后續(xù)再講這個int32 NumProperties; //同上const char* ClassConfigNameUTF8;const FCppClassTypeInfoStatic* CppClassInfo;const FImplementedInterfaceParams* ImplementedInterfaceArray; //本例子中沒用上int32 NumImplementedInterfaces; // 0 #if WITH_METADATAconst FMetaDataPairParam* MetaDataArray;int32 NumMetaData; #endif };以下是ConstructUclass的部分內(nèi)容。
根據(jù)流程圖,可以看到,首先是執(zhí)行的DependecySingletonFunctions,檢測自己所依賴的單例是否都建立了反射關(guān)系。接著就是StaticClass函數(shù)部分,這也是我們重點關(guān)注的部分。在StaticClass,實現(xiàn)了構(gòu)建了一個基礎(chǔ)的UClass的反射,但是還沒有完善細節(jié),因此在后續(xù)部分給NewClass添加細節(jié)部分。
步驟:
執(zhí)行DependecySingletonFunctions數(shù)組中所依賴的函數(shù),確保自身的父類反射已經(jīng)被構(gòu)建和自身所在的Package反射被構(gòu)建。
UObjectForceRegistration,把自己從等待注冊的Map中去掉, 并且注冊。
ConstructUFunction,組建Function,并且注冊Function。(這部分后續(xù)再探討
ConstructUProperties,組建Properties。(這部分后續(xù)再探討
注冊階段
注冊階段主要分為兩個過程,首先構(gòu)建UClass的時候添加進等待注冊表,等待注冊,在收集完成UClass所有信息的時候,延遲注冊。
注冊過程分析
Ue4是如何完成注冊的呢?
其實就是把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 ); }通過上面的代碼,我們可以發(fā)現(xiàn)通過名字生成Index和通過OuterName(也就是PackageName)來生成Index,然后添加進Map內(nèi)部中。具體如何添加進HashMap中呢?請看下面的分析。
TUObjectHashTables
在這里就不得不介紹這個類TUObjectHashTables了,主要的存儲的類。注冊的反射的數(shù)據(jù)都以存放在這里。
首先看看這個類的定義。
首先這個類用了餓漢單例的設(shè)計模式,保證只有一個存儲數(shù)據(jù)的實例,
從這個類中我們可以看到有5個Map,具體的映射方式也都顯而易見了,看看我們注冊的時候用了這幾個Map是怎樣處理的。
void HashObject(UObjectBase* Object) {auto& ThreadHash = FUObjectHashTables::Get(); //獲取存儲單例Hash = GetObjectHash(Name); //把FName轉(zhuǎn)化為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} }總結(jié)
以上是生活随笔為你收集整理的UE4反射原理(转)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python决策树结果图_Python决
- 下一篇: 比较器之几种电压比较器电路