上一篇翻譯的文章里面提到了UE4反射系統的基本原理與應用,這次我們通過代碼來深入研究一下UE4的反射系統,因為反射系統在UE4中牽扯的東西較多,所以我打算分幾篇文章分析。我這里假定讀者對UE4有一定的了解並且有一定的C++基礎,如果不了解UE4如何使用,那么請先學會如何使用UE4引擎,否則看起來可能會比較困難。
以下是我整理的一個跟反射系統相關的類圖:
從上面可以看出UObject是整個反射系統核心,UE4中支持反射的類型在上一篇文章中已經說過,包括 C++類、結構體、函數 、成員變量以及枚舉,也支持TArray(只支持一些如TArray和TSubclassOf的模板類型,並且它們的模板類型不能是嵌套的類型),但是TMap不支持。而這些東西的支持與上面的類是分不開的,比如UClass、UBlueprintGeneratedClass、UFunction、UEnum、以及UProperty,以及繼承自它們的子類。每一個繼承UObject且支持反射系統類型都有一個相對應 的UClass,或者它的子類(比如藍圖對應的課表UBlueprintGeneratedClass類,它繼承自UClass),如果是特定的藍圖類型,比如動作藍圖、Widget藍圖等,如上圖所示。UMetaData是元數據,它存儲了一些編輯器需要的額外信息,比如它的分類(Category )、提示(Tooltip)等,最終打包的時候是不會用到這些信息的。至於我們反射系統里需要訪問的float、int32等變量,則都是由繼承自UProperty的子類來表示的,具體 可以根據上圖所列出的對象去代碼里面去找對應的類去看它具體的實現。
下面我們以一個最簡單的代碼示例來說明UE4中反射的實現過程,首先我創建了一個名為ReflectionStudy的工程(只有Basic Code),這樣做是為了方便分析代碼,一開始提到的文章中說過,如果你想讓你實現的類支持反射,那么必須遵循相關的准則,比如要使用UENUM()、UCLASS()、USTRUCT()、UFUNCTION()、以及UPROPERTY()等,UHT會根據這些宏來生成對應的支持反射的代碼。下面我們分別展開來分析這些代碼,它生成的代碼都存放在你的工程ReflectionStudy\Intermediate\Build\Win64\UE4Editor\Inc\ReflectionStudy路徑下。
里面一般分為幾類文件:
- ReflectionStudy.generated.cpp 一個工程只有一個,這個文件是用來為每個支持反射的類生成反射信息的代碼,比如注冊屬性、添加源數據等。
- ReflectionStudy.generated.dep.h 這個文件里面就是包含了上面1. ReflectionStudy.generated.cpp用到的頭文件。
- ReflectionStudyClasses.h
- *.generated.h 這個就是為每個支持反射的頭文件生成的對應的宏的代碼。
類
類的定義
我們以下面的代碼為例來講解,為了查看一些用法的具體實現,我們特意加了以下幾個 屬性和方法。
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "GameFramework/GameMode.h" #include "ReflectionStudyGameMode.generated.h" /** * */ UCLASS() class REFLECTIONSTUDY_API AReflectionStudyGameMode : public AGameMode { 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生成的.generated.h文件
因為對應的ReflectionStudyGameMode.generated.h頭文件較長,所以我們只把關鍵的部分列出來講解。
#define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_RPC_WRAPPERS_NO_PURE_DECLS \ virtual void NavtiveFuncTest_Implementation(); \ \ DECLARE_FUNCTION(execNavtiveFuncTest) \ { \ P_FINISH; \ P_NATIVE_BEGIN; \ this->NavtiveFuncTest_Implementation(); \ P_NATIVE_END; \ } \ \ DECLARE_FUNCTION(execCallableFuncTest) \ { \ P_FINISH; \ P_NATIVE_BEGIN; \ this->CallableFuncTest(); \ P_NATIVE_END; \ }
可以看到,我們上面定義的函數,UHT幫我們自動生成了如上代碼,至於為什么會生成這樣的函數,那是因為UE4藍圖調用約定,每個函數前面要加一個exec前綴,關於藍圖的實現因為我目前也了解的也不是很清楚,所以可能會在后面出一個對藍圖實現的介紹,這些函數都是由UE4虛擬機調用過來的,如果包含參數和返回值,那么還會有相應的從虛擬機棧上取參數和設置返回值的代碼,讀者可以自行去驗證。
define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_INCLASS_NO_PURE_DECLS \ private: \ static void StaticRegisterNativesAReflectionStudyGameMode(); \ friend REFLECTIONSTUDY_API class UClass* Z_Construct_UClass_AReflectionStudyGameMode(); \ public: \ DECLARE_CLASS(AReflectionStudyGameMode, AGameMode, COMPILED_IN_FLAGS(0 | CLASS_Transient | CLASS_Config), 0, TEXT("/Script/ReflectionStudy"), NO_API) \ DECLARE_SERIALIZER(AReflectionStudyGameMode) \ /** Indicates whether the class is compiled into the engine */ \ enum {IsIntrinsic=COMPILED_IN_INTRINSIC};
- StaticRegisterNativesAReflectionStudyGameMode 這個函數是用來 注冊C++原生函數暴露給虛擬機使用的。
- friend REFLECTIONSTUDY_API class UClass* Z_Construct_UClass_AReflectionStudyGameMode(); 聲明友元函數,這個函數是用來構建此類對應的UClass的。
- DECLARE_CLASS 此宏比較復雜,主要是定義了StaticClass() 等,具體實現請讀者打開它的定義就可以看到。
- DECLARE_SERIALIZER 定義序列化代碼。
- enum {IsIntrinsic=COMPILED_IN_INTRINSIC}; 正如注釋所說,就是用來標記這個類是否是編譯到引擎中的。
#define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_ENHANCED_CONSTRUCTORS \ /** Standard constructor, called after all reflected properties have been initialized */ \ NO_API AReflectionStudyGameMode(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \ private: \ /** Private copy-constructor, should never be used */ \ NO_API AReflectionStudyGameMode(const AReflectionStudyGameMode& InCopy); \ public: \ DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, AReflectionStudyGameMode); \ DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(AReflectionStudyGameMode); \ DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AReflectionStudyGameMode)
- NO_API AReflectionStudyGameMode(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \定義一個標准構造函數,在所有反射屬性都初始化之后調用。
- NO_API AReflectionStudyGameMode(const AReflectionStudyGameMode& InCopy); \ 防止調用拷貝構造函數
- DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, AReflectionStudyGameMode); \ DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(AReflectionStudyGameMode); \ 熱加載相關,這是UE4里面比較牛逼的功能,我們這里也不詳細討論,這個如果以后對這塊理解了也會單獨開個專題進行講解。
-
DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL 定義了一個默認構造函數,如下代碼所示:
static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }
#define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_GENERATED_BODY \ PRAGMA_DISABLE_DEPRECATION_WARNINGS \ public: \ ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_RPC_WRAPPERS_NO_PURE_DECLS \ ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_CALLBACK_WRAPPERS \ ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_INCLASS_NO_PURE_DECLS \ ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_ENHANCED_CONSTRUCTORS \ private: \ PRAGMA_ENABLE_DEPRECATION_WARNINGS
這段代碼就是對上述解釋宏的引用,配合下面這個宏最終就實現了在class中定義一個GENERATED_BODY()就可以把上面所有定義的內容包含到該類中。
#undef CURRENT_FILE_ID #define CURRENT_FILE_ID ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h
所有GENERATED_BODY()相關的宏定義如下
// This pair of macros is used to help implement GENERATED_BODY() and GENERATED_USTRUCT_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_LEGACY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY_LEGACY) #define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY) #define GENERATED_USTRUCT_BODY(...) GENERATED_BODY() #define GENERATED_UCLASS_BODY(...) GENERATED_BODY_LEGACY() #define GENERATED_UINTERFACE_BODY(...) GENERATED_BODY_LEGACY() #define GENERATED_IINTERFACE_BODY(...) GENERATED_BODY_LEGACY()
至此ReflectionStudyGameMode.generated.h文件里面的內容就基本分析完了,下面我們來看ReflectionStudy.generated.cpp里面對應的代碼,結合前面的解釋,相信你對整個UE4的反射系統就有一個大體的了解了。
.generated.cpp文件中相關內容
FName REFLECTIONSTUDY_ImplementableFuncTest = FName(TEXT("ImplementableFuncTest")); FName REFLECTIONSTUDY_NavtiveFuncTest = FName(TEXT("NavtiveFuncTest")); void AReflectionStudyGameMode::ImplementableFuncTest() { ProcessEvent(FindFunctionChecked(REFLECTIONSTUDY_ImplementableFuncTest),NULL); } void AReflectionStudyGameMode::NavtiveFuncTest() { ProcessEvent(FindFunctionChecked(REFLECTIONSTUDY_NavtiveFuncTest),NULL); } void AReflectionStudyGameMode::StaticRegisterNativesAReflectionStudyGameMode() { FNativeFunctionRegistrar::RegisterFunction(AReflectionStudyGameMode::StaticClass(), "CallableFuncTest",(Native)&AReflectionStudyGameMode::execCallableFuncTest); FNativeFunctionRegistrar::RegisterFunction(AReflectionStudyGameMode::StaticClass(), "NavtiveFuncTest",(Native)&AReflectionStudyGameMode::execNavtiveFuncTest); } IMPLEMENT_CLASS(AReflectionStudyGameMode, 3618622309);
- 剛接觸UE4的時候,如果是BlueprintImplementabeEvent的函數,是不是發現不需要自己去實現,那么當時有沒有覺得怪異呢,上面的代碼就解釋清楚了,那是UE4幫我們實現了,可以看到它調用了ProcessEvent方法,這個方法在UObject中實現的。
- StaticRegisterNativesAReflectionStudyGameMode 向AReflectionStudyGameMode::StaticClass()返回的UClass里面添加原生的C++函數。
- IMPLEMENT_CLASS 定義了一個靜態全局變量,用於在程序啟動的時候注冊UClass。
UFunction* Z_Construct_UFunction_AReflectionStudyGameMode_CallableFuncTest() { UObject* Outer=Z_Construct_UClass_AReflectionStudyGameMode(); static UFunction* ReturnFunction = NULL; if (!ReturnFunction) { ReturnFunction = new(EC_InternalUseOnlyConstructor, Outer, TEXT("CallableFuncTest"), RF_Public|RF_Transient|RF_MarkAsNative) UFunction(FObjectInitializer(), NULL, 0x04080401, 65535); ReturnFunction->Bind(); ReturnFunction->StaticLink(); #if WITH_METADATA UMetaData* MetaData = ReturnFunction->GetOutermost()->GetMetaData(); MetaData->SetValue(ReturnFunction, TEXT("Category"), TEXT("AReflectionStudyGameMode")); MetaData->SetValue(ReturnFunction, TEXT("ModuleRelativePath"), TEXT("ReflectionStudyGameMode.h")); #endif } return ReturnFunction; }
- 這個函數向AReflectionStudyGameMode返回的UClass類里面注冊名為CallableFuncTest的函數,而#if WITH_METADATA里面就是我們前面提到的元數據,可以注意其中我們類中指定的Category分類就在這里指定的,放在了它的(UPackage中)UMetaData中。Z_Construct_UFunction_AReflectionStudyGameMode_ImplementableFuncTest()和Z_Construct_UFunction_AReflectionStudyGameMode_NavtiveFuncTest()實現方式和上面基本一樣,這里我就不寫出來了。
UClass* Z_Construct_UClass_AReflectionStudyGameMode() { static UClass* OuterClass = NULL; if (!OuterClass) { Z_Construct_UClass_AGameMode(); Z_Construct_UPackage__Script_ReflectionStudy(); OuterClass = AReflectionStudyGameMode::StaticClass(); if (!(OuterClass->ClassFlags & CLASS_Constructed)) { UObjectForceRegistration(OuterClass); OuterClass->ClassFlags |= 0x2090028C; OuterClass->LinkChild(Z_Construct_UFunction_AReflectionStudyGameMode_CallableFuncTest()); OuterClass->LinkChild(Z_Construct_UFunction_AReflectionStudyGameMode_ImplementableFuncTest()); OuterClass->LinkChild(Z_Construct_UFunction_AReflectionStudyGameMode_NavtiveFuncTest()); PRAGMA_DISABLE_DEPRECATION_WARNINGS UProperty* NewProp_Score = new(EC_InternalUseOnlyConstructor, OuterClass, TEXT("Score"), RF_Public|RF_Transient|RF_MarkAsNative) UFloatProperty(CPP_PROPERTY_BASE(Score, AReflectionStudyGameMode), 0x0020080000000004); PRAGMA_ENABLE_DEPRECATION_WARNINGS OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_AReflectionStudyGameMode_CallableFuncTest(), "CallableFuncTest"); // 3059784748 OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_AReflectionStudyGameMode_ImplementableFuncTest(), "ImplementableFuncTest"); // 4773450 OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_AReflectionStudyGameMode_NavtiveFuncTest(), "NavtiveFuncTest"); // 2500148308 OuterClass->ClassConfigName = FName(TEXT("Game")); OuterClass->StaticLink(); #if WITH_METADATA UMetaData* MetaData = OuterClass->GetOutermost()->GetMetaData(); MetaData->SetValue(OuterClass, TEXT("HideCategories"), TEXT("Info Rendering MovementReplication Replication Actor Input Movement Collision Rendering Utilities|Transformation")); MetaData->SetValue(OuterClass, TEXT("IncludePath"), TEXT("ReflectionStudyGameMode.h")); MetaData->SetValue(OuterClass, TEXT("ModuleRelativePath"), TEXT("ReflectionStudyGameMode.h")); MetaData->SetValue(OuterClass, TEXT("ShowCategories"), TEXT("Input|MouseInput Input|TouchInput")); MetaData->SetValue(NewProp_Score, TEXT("Category"), TEXT("AReflectionStudyGameMode")); MetaData->SetValue(NewProp_Score, TEXT("ModuleRelativePath"), TEXT("ReflectionStudyGameMode.h")); #endif } } check(OuterClass->GetClass()); return OuterClass; }
- 這個函數的作用就是來生成AReflectionStudyGameMode的UClass對象,並注冊所有的UFunction 和UProperty
- Z_Construct_UClass_AGameMode(); 因為它繼承自AGameMode所以AGameMode的UClass必須有效。
- Z_Construct_UPackage__Script_ReflectionStudy(); 確保UPackage已經創建。
- #if WITH_METADATA 宏中代碼也是用於創建元數據。
static FCompiledInDefer Z_CompiledInDefer_UClass_AReflectionStudyGameMode(Z_Construct_UClass_AReflectionStudyGameMode, &AReflectionStudyGameMode::StaticClass, TEXT("AReflectionStudyGameMode"), false, nullptr, nullptr); DEFINE_VTABLE_PTR_HELPER_CTOR(AReflectionStudyGameMode);
- 第一行代碼用於存放創建UClass的一個靜態函數,之后將會執行這個靜態生成UClass函數
- DEFINE_VTABLE_PTR_HELPER_CTOR 定義一個參數為FVTableHelper構造函數。
UPackage* Z_Construct_UPackage__Script_ReflectionStudy() { static UPackage* ReturnPackage = NULL; if (!ReturnPackage) { ReturnPackage = CastChecked<UPackage>(StaticFindObjectFast(UPackage::StaticClass(), NULL, FName(TEXT("/Script/ReflectionStudy")), false, false)); ReturnPackage->SetPackageFlags(PKG_CompiledIn | 0x00000000); FGuid Guid; Guid.A = 0x00B770A5; Guid.B = 0x8BECE3AF; Guid.C = 0x00000000; Guid.D = 0x00000000; ReturnPackage->SetGuid(Guid); } return ReturnPackage; }
用於返當前模塊的UPackage,上面的代碼中會用到這個參數GetOuterMost()函數,返回的就是這個UPackage。
總結
至此我們對UE4中反射系統對類的支持做了一個簡單的介紹,相信大家也有了一定的了解,限於篇幅,我們這篇到此為止,后面會繼續討論其它USTRUCT、UENUM、等的實現,以及它們整個反射系統的運行流程。由於我對UE4也不是特別熟悉,所以其中可能有說的不准確的地方,如果有錯誤的地方,還請指正,也希望大家能一起討論。當然后面我也會講一下UE4中其它模塊的實現,比如整個藍圖的實現、多線程渲染、以及基於物理的渲染等內容。
由於最上面的類圖尺寸過大,上傳后的圖片並不是特別清晰,高清原圖可以在這里下載。