什么是反射?或者說反射能做什么,簡單來說,反射可以提供一種能力,能夠在運(yùn)行時動態(tài)獲取對象的成員信息,如成員函數(shù),成員變量。UE 在其反射系統(tǒng)上支持了許多功能,如:
編輯器中可供編輯的屬性GC序列化網(wǎng)絡(luò)同步1.1 使用反射的準(zhǔn)備工作UE 中應(yīng)用反射需要與它定義的宏相結(jié)合,主要有 3 種類型,如下所示:
類注冊#include "Weapon.generated.h" // 包含自動生成的頭文件信息UCLASS() // 注冊類信息class AWeapon : public AActor {GENERATED_BODY() // 生成類輔助代碼public:UPROPERTY() // 注冊類屬性FName WeaponName;UFUNCTION() // 注冊類成員函數(shù)void Fire();}
結(jié)構(gòu)體注冊(需要注意的是,UFUNCTION
只能在 Class 中使用)#include "Weapon.generated.h" // 包含自動生成的頭文件信息USTRUCT() // 注冊結(jié)構(gòu)體struct FWeapon {UPROPERTY() // 注冊結(jié)構(gòu)體屬性FName WeaponName;}
枚舉注冊#include "Weapon.generated.h" // 包含自動生成的頭文件信息UENUM() // 注冊枚舉信息enum WeaponType {Short,Middle,Far,}
1.2 反射的簡單應(yīng)用前面注冊完畢反射后,就能簡單的使用反射了,如下:
(資料圖片)
#include "human.generated.h" // 包含自動生成的頭文件信息/** UHuman.h **/class UHuman {public:UPROPERTY()FString Name = "Hello, Reflection!!!";UPROPERTY()UHuman* Child;}UHuman* Human = NewObject();UClass* UCHuman = UHuman::StaticClass();// 轉(zhuǎn)為對應(yīng)的Propertyif (FStrProperty* StrProperty = CastField(Property)){ // 取Property地址(因?yàn)閷傩韵到y(tǒng)知道屬性在類內(nèi)存中的偏移值) void* PropertyAddr = StrProperty->ContainerPtrToValuePtr(Human); // 通過地址取值(其實(shí)就是類型轉(zhuǎn)換,畢竟我們都拿到內(nèi)存地址了) FString PropertyValue = StrProperty->GetPropertyValue(PropertyAddr); UE_LOG(LogTemp, Warning, TEXT("Property"s Value is %s"), *PropertyValue);}
但是這種使用只是最粗淺的使用,更多時候反射的應(yīng)用對我們來說是無感知的,如網(wǎng)絡(luò)同步,編輯器的屬性編輯等,都是建立在反射系統(tǒng)之上的,反射系統(tǒng)更多是一個基層系統(tǒng),輔助構(gòu)建其他高層次的系統(tǒng)。
2. 反射整體結(jié)構(gòu)UE 的反射系統(tǒng)其整體的結(jié)構(gòu)如下:
總體來說,其各種結(jié)構(gòu)對應(yīng)收集不同類型的反射信息:
UClass :收集類數(shù)據(jù),描述一個類的成員變量,函數(shù),父類等信息UEnum:收集枚舉數(shù)據(jù)UScriptStruct :收集結(jié)構(gòu)體數(shù)據(jù)UFunction:收集函數(shù)信息以 UClass 為例,其采用FProperty
來儲存所有的簡單屬性信息(如 Bool
,Int
),而一些復(fù)合類型數(shù)據(jù)則使用 UField
存儲(如 AActor
,TArray
)。這里需要認(rèn)識到:UClass 等反射結(jié)構(gòu)其本質(zhì)上只是描述一個類的結(jié)構(gòu),本身與業(yè)務(wù)類無實(shí)際耦合關(guān)系,每個標(biāo)記了 UCLASS(...)
宏的 class 都會有一個 UClass* Object
儲存其反射信息。3. 構(gòu)建流程從寫代碼的角度來說,我們只需要對變量,類等定義標(biāo)注一個 宏,再 include 一個頭文件就完事了,具體構(gòu)建的過程則是由 UE 的編譯工具去完成的。也就是 Unreal Build Tool(UBT) 和 Unreal Header Tool(UHT)。接下來以前面的 class AWeapon
為例,展示其自動生成的內(nèi)容和如何初始化其反射信息。
3.1 自動生成文件[!note]UHT是一個用于預(yù)處理源代碼文件的工具,它可以識別
UCLASS
、UFUNCTION
等宏,并通過生成額外的 C++ 代碼來擴(kuò)展類的功能。UHT還可以用于生成反射信息,例如類的元數(shù)據(jù)和屬性信息,以便在運(yùn)行時進(jìn)行藍(lán)圖交互等操作。UBT是一個用于編譯和鏈接 UE4 項(xiàng)目的構(gòu)建系統(tǒng)。它可以自動管理項(xiàng)目中的依賴項(xiàng),并生成可執(zhí)行文件和動態(tài)鏈接庫等二進(jìn)制文件。UBT 還可以執(zhí)行諸如打包、部署和測試等其他任務(wù)。
兩個工具在 UE4 開發(fā)中密切相關(guān),因?yàn)?UHT生成的反射信息需要在 UBT中使用,以便生成最終的可執(zhí)行文件和動態(tài)鏈接庫。因此,在構(gòu)建 UE4 項(xiàng)目時,UBT將首先調(diào)用 UHT來處理源代碼文件,然后使用生成的代碼來編譯和鏈接項(xiàng)目。
在 [[原理#^644683|1.1 使用反射的準(zhǔn)備工作]] 中,主要工作分為兩步:
標(biāo)注宏信息(如UCLASS
,UFUNCTION
,UPROPERTY
)包含頭文件 #include ${filename}.generated.h
這里頭文件是利用 UHT 工具掃描生成的,其附帶還會生成一個 ${filename}.gen.cpp
的源文件。這兩個文件主要負(fù)責(zé)兩件事情:定義一個或多個輔助類(根據(jù) UCLASS
和 USTRUCT
等標(biāo)注的結(jié)構(gòu)數(shù)量),收集標(biāo)注了宏信息的結(jié)構(gòu),該輔助類構(gòu)造函數(shù)會返回一個構(gòu)造好的 UClass
定義一個 FCompileDeferInfo
靜態(tài)變量,其構(gòu)造函數(shù)會在啟動時將輔助類的信息導(dǎo)入到一個全局的容器中,啟動時會遍歷這個容器,構(gòu)建好 UClass 等反射信息。其大致流程如下:3.2 預(yù)生成代碼接下來分析預(yù)先生成的 generated.h 和 gen.cpp 都做了什么事情一個 Class 需要注冊反射信息時,其使用方式如下(有一個必要的前提條件為該 Class 的繼承鏈中需要有 UObject
):
#include "Weapon.generated.h" // 包含自動生成的頭文件信息UCLASS() // 注冊類信息class AWeapon : public AActor {GENERATED_BODY() // 生成類輔助代碼public:UPROPERTY() // 注冊類屬性FName WeaponName;UFUNCTION() // 注冊類成員函數(shù)void Fire();}
可以看到其相關(guān)的宏主要有如下幾個:
UCLASSGENERATED_BODYUPROPERTYUFUNCTION這里首先需要了解這些宏背后都做了什么3.2.1 宏展開關(guān)鍵的宏定義如下:
/* 將 ABCD 4 個名稱鏈接起來*/#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 UCLASS(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_PROLOG)#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY);/* 純標(biāo)記,用給 UHT 掃描 */#define UPROPERTY(...) #define UFUNCTION(...)
以 3.2 的示例為例,展開后內(nèi)容大致如下:
UdemyProject_Source_UdemyProject_AWeapon_h_3_PROLOG // 注冊類信息class AWeapon : public AActor {UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY // 生成類輔助代碼public:UPROPERTY() // 由于是標(biāo)記,這里展開之后是沒有特殊信息的FName WeaponName;UFUNCTION() // 由于是標(biāo)記,這里展開之后是沒有特殊信息的void Fire();}
可以看到展開后是一個個神秘的符號,其實(shí)這都是宏的名稱,其定義在自動生成的 generated.h 文件中。這里展示了一個特點(diǎn),盡管不同的類都使用的相同的宏,但是 UHT 還是能保證掃描生成的文件信息唯一性。這里主要關(guān)注兩個宏:
GENERATED_BODY_LEGACYGENERATED_BODY接著展示一下兩個宏其對應(yīng)的文件信息。#define UdemyProject_Source_UdemyProject_AWeapon_h_3_PROLOG #define UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY_LEGACY \ PRAGMA_DISABLE_DEPRECATION_WARNINGS \ public: \ UdemyProject_Source_UdemyProject_AWeapon_h_5_PRIVATE_PROPERTY_OFFSET \ UdemyProject_Source_UdemyProject_AWeapon_h_5_SPARSE_DATA \ UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS \ UdemyProject_Source_UdemyProject_AWeapon_h_5_INCLASS \ UdemyProject_Source_UdemyProject_AWeapon_h_5_STANDARD_CONSTRUCTORS \ public: \#define UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY \ PRAGMA_DISABLE_DEPRECATION_WARNINGS \ public: \ UdemyProject_Source_UdemyProject_AWeapon_h_5_PRIVATE_PROPERTY_OFFSET \ UdemyProject_Source_UdemyProject_AWeapon_h_5_SPARSE_DATA \ UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS_NO_PURE_DECLS \ UdemyProject_Source_UdemyProject_AWeapon_h_5_INCLASS_NO_PURE_DECLS \ UdemyProject_Source_UdemyProject_AWeapon_h_5_ENHANCED_CONSTRUCTORS \ private: \
可以看到 GENERATED_BODY_LEGACY
和 GENERATED_BODY
的內(nèi)容基本一致,查閱資料發(fā)現(xiàn)這主要是為了前向兼容。因此可以先忽略 GENERATED_BODY_LEGACY
內(nèi)容,關(guān)注 GENERATED_BODY
的內(nèi)容??梢钥吹?GENERATED_BODY
又嵌套了一堆宏(宏的定義在自動生成的 generated.h 頭文件),其展開之后才是真正的代碼,比如
/* UFUNCTION Wrapper 函數(shù) */#define UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS_NO_PURE_DECLS \ DECLARE_FUNCTION(execFire);
可以對其完整展開,還原其最終的樣貌
/* 該宏可以忽略 */UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY_LEGACY class AWeapon : public AActor {public:/* UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS_NO_PURE_DECLSUFunction 的 Wrapper Function 集合*/static void execFire( UObject* Context, FFrame& Stack, RESULT_DECL );private: static void StaticRegisterNativesAWeapon(); friend struct Z_Construct_UClass_AWeapon_Statics; public: /* DECLARE_CLASS(AWeapon, AActor, COMPILED_IN_FLAGS(0 | CLASS_Config), CASTCLASS_None, TEXT("/Script/UdemyProject"), NO_API) 類輔助定義相關(guān) */private: \ AWeapon& operator=(AWeapon&&); \ AWeapon& operator=(const AWeapon&); \ TRequiredAPI static UClass* GetPrivateStaticClass(); \ public: \ /** Bitwise union of #EClassFlags pertaining to this class.*/ \ enum {StaticClassFlags=COMPILED_IN_FLAGS(0 | CLASS_Config}; \ /** Typedef for the base class ({{ typedef-type }}) */ \ typedef AActor Super;\ /** Typedef for {{ typedef-type }}. */ \ typedef AWeapon ThisClass;\ /** Returns a UClass object representing this class at runtime */ \ inline static UClass* StaticClass() \ { \ return GetPrivateStaticClass(); \ } \ /** Returns the package this class belongs in */ \ inline static const TCHAR* StaticPackage() \ { \ return TEXT("/Script/UdemyProject"); \ } \ /** Returns the static cast flags for this class */ \ inline static EClassCastFlags StaticClassCastFlags() \ { \ return CASTCLASS_None; } /** For internal use only; use StaticConstructObject() to create new objects. */ inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags) { return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags); } /** For internal use only; use StaticConstructObject() to create new objects. */ inline void* operator new( const size_t InSize, EInternal* InMem ) \ { return (void*)InMem; }/* 序列化相關(guān) */friend FArchive &operator<<( FArchive& Ar, AWeapon*& Res ) { return Ar << (UObject*&)Res; }friend void operator<<(FStructuredArchive::FSlot InSlot, AWeapon*& Res) \ { InSlot << (UObject*&)Res;}/* 構(gòu)造函數(shù)相關(guān) */ /** Standard constructor, called after all reflected properties have been initialized */ NO_API AWeapon(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; private: /** Private move- and copy-constructors, should never be used */ NO_API AWeapon(AWeapon&&); NO_API AWeapon(const AWeapon&); public: /* 默認(rèn)構(gòu)造函數(shù) */NO_API AWeapon(FVTableHelper& Helper);static UObject* __VTableCtorCaller(FVTableHelper& Helper){ return new (EC_InternalUseOnlyConstructor, (UObject*)GetTransientPackage(), NAME_None, RF_NeedLoad | RF_ClassDefaultObject | RF_TagGarbageTemp) AWeapon(Helper);} static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())AWeapon(X); }public:UPROPERTY() // 注冊類屬性FName WeaponName;UFUNCTION() // 注冊類成員函數(shù)void Fire();}
可以看到 GENERATED_BODY
宏為 AWeapon
擴(kuò)展了很多功能,包括但不限于:
gen.cpp 的內(nèi)容主要為構(gòu)建好描述 AWeapon
反射信息的 UClass
首先以 AWeapon::Fire
為例,對其標(biāo)記 UFUNCTION
后檢查其生成的相關(guān)內(nèi)容大致如下:
DEFINE_FUNCTION(AWeapon::execFire) { P_FINISH; P_NATIVE_BEGIN; P_THIS->Fire(); // 實(shí)際上就是調(diào)用了下 Navtive 的 Fire 函數(shù) P_NATIVE_END; }
生成一個結(jié)構(gòu)體 FFunctionParams
,儲存構(gòu)建 UFunction
所需的參數(shù),并提供構(gòu)建 UFunction
的方法,參數(shù)內(nèi)容主要分為:函數(shù)的標(biāo)記(比如標(biāo)記為 Server 或者 Client 等)函數(shù)的名稱函數(shù)的參數(shù)和返回值(其統(tǒng)一用一個 List 存儲,每個元素會有一個 Flag 標(biāo)記其是引用還是返回值還是普通參數(shù))參數(shù)的數(shù)量/* 定義一個結(jié)構(gòu)體,參數(shù)為構(gòu)建一個 UFunction 所需要的參數(shù) */ struct Z_Construct_UFunction_AWeapon_Fire_Statics { static const UE4CodeGen_Private::FFunctionParams FuncParams; }; /* 初始化一個結(jié)構(gòu)體 */ const UE4CodeGen_Private::FFunctionParams Z_Construct_UFunction_AWeapon_Fire_Statics::FuncParams = { (UObject*(*)())Z_Construct_UClass_AWeapon, nullptr, "Fire", nullptr, nullptr, 0, nullptr, 0, RF_Public|RF_Transient|RF_MarkAsNative,(EFunctionFlags)0x00020401,0,0, METADATA_PARAMS(Z_Construct_UFunction_AWeapon_Fire_Statics::Function_MetaDataParams, UE_ARRAY_COUNT(Z_Construct_UFunction_AWeapon_Fire_Statics::Function_MetaDataParams)) }; /* 生成一個構(gòu)造方法,用來構(gòu)造 AWeapon::Fire 的 UFunction 信息 */UFunction* Z_Construct_UFunction_AWeapon_Fire() { static UFunction* ReturnFunction = nullptr; if (!ReturnFunction) { UE4CodeGen_Private::ConstructUFunction(ReturnFunction, Z_Construct_UFunction_AWeapon_Fire_Statics::FuncParams); } return ReturnFunction; }
UPROPERTY 相關(guān)代碼類似生成 UFunction
,此處由于 WeaponName
是基礎(chǔ)類型,所以直接初始化一個 FNamePropertyParams
的結(jié)構(gòu)體。這里面就包含了:
const UE4CodeGen_Private::FNamePropertyParams Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName = { "WeaponName",nullptr,(EPropertyFlags)0x0010000000000000,UE4CodeGen_Private::EPropertyGenFlags::Name,RF_Public|RF_Transient|RF_MarkAsNative,1,STRUCT_OFFSET(AWeapon, WeaponName), METADATA_PARAMS(Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName_MetaData,UE_ARRAY_COUNT(Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName_MetaData)) };
UCLASS 相關(guān)代碼前面定義的函數(shù)和成員變量的代碼都已經(jīng)生成完畢了,接下來看具體是如何將其結(jié)合到 Class 中的。首先 gen.cpp 中會生成代碼將 Function 和 Property 分開存儲,定義如下:
/** 成員變量 **/const UE4CodeGen_Private::FPropertyParamsBase* const Z_Construct_UClass_AWeapon_Statics::PropPointers[] = { (const UE4CodeGen_Private::FPropertyParamsBase*)&Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName, };/** 成員函數(shù) **/const FClassFunctionLinkInfo Z_Construct_UClass_AWeapon_Statics::FuncInfo[] = { { &Z_Construct_UFunction_AWeapon_Fire, "Fire" }, // 2996945510 };
接著提供構(gòu)建 AWeapon
的 UClass
信息,類似構(gòu)建 UFunction
一般,其填充了一個 FClassParams
的結(jié)構(gòu)體,主要內(nèi)容包括但不限于:
const UE4CodeGen_Private::FClassParams Z_Construct_UClass_AWeapon_Statics::ClassParams = { &AWeapon::StaticClass, "Engine", &StaticCppClassTypeInfo, DependentSingletons, FuncInfo, Z_Construct_UClass_AWeapon_Statics::PropPointers, nullptr, UE_ARRAY_COUNT(DependentSingletons), UE_ARRAY_COUNT(FuncInfo), UE_ARRAY_COUNT(Z_Construct_UClass_AWeapon_Statics::PropPointers), 0, 0x008000A4u, METADATA_PARAMS(Z_Construct_UClass_AWeapon_Statics::Class_MetaDataParams, UE_ARRAY_COUNT(Z_Construct_UClass_AWeapon_Statics::Class_MetaDataParams)) };
然后提供一個構(gòu)建 UClass
的接口
UClass* Z_Construct_UClass_AWeapon() { static UClass* OuterClass = nullptr; if (!OuterClass) { UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_AWeapon_Statics::ClassParams); } return OuterClass; }
至此,整個類的自動生成的反射代碼基本描述完了。
3.2.3 小結(jié)3.2 主要闡述自動生成的代碼內(nèi)容大致是什么東西,個人認(rèn)為主要分為如下幾點(diǎn):
為AWeapon
增加輔助接口(比如 Super,StaticClass,構(gòu)造函數(shù)等)生成 AWeapon
中所有標(biāo)記了 UPROPERTY
和 UFUNCTION
的反射代碼和構(gòu)建接口生成 AWeapon
這個 Class 的反射代碼和構(gòu)建接口最后將接口暴露出去給引擎初始化調(diào)用即可。3.3 初始化反射信息3.2 中預(yù)生成的代碼已經(jīng)封裝好所有反射結(jié)構(gòu)的接口了,接下來只要調(diào)用就可以生成 AWeapon
的反射信息了。
UE 中反射信息主要是在引擎啟動時初始化的,主要利用 gen.cpp 中自動生成的一個靜態(tài)變量
static FCompiledInDefer Z_CompiledInDefer_UClass_AWeapon(Z_Construct_UClass_AWeapon, &AWeapon::StaticClass, TEXT("/Script/UdemyProject"), TEXT("AWeapon"), false, nullptr, nullptr, nullptr);
其構(gòu)造函數(shù)會將 構(gòu)造 AWeapon
的 反射接口傳入到一個全局容器,啟動時會調(diào)用 UObjectLoadAllCompiledInDefaultProperties
遍歷構(gòu)造好 UClass。大致偽代碼如下:
// DeferredCompiledInRegistration 存儲了 Z_Construct_UClass_AWeaponstatic void UObjectLoadAllCompiledInDefaultProperties(){TArray PendingRegistrants = MoveTemp(DeferredCompiledInRegistration); for (UClass* (*Registrant)() : PendingRegistrants) { // 此處調(diào)用 Registrant,也就會調(diào)用 Z_Construct_UClass_AWeaponUClass* Class = Registrant(); /* 省略一些代碼 */NewClasses.Add(Class); }}
3.3.2 構(gòu)建反射信息AWeapon 反射信息的構(gòu)建入口如下:
UClass* Z_Construct_UClass_AWeapon() { static UClass* OuterClass = nullptr; if (!OuterClass) { UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_AWeapon_Statics::ClassParams); } return OuterClass; }
即使有多個 AWeapon 對象也是共用一個 UClass 來描述反射信息。其具體的調(diào)用鏈如下(下面的 AWeapon 可替換為任意自定義的 Class):
4. QA4.1 如何利用 UClass 構(gòu)建一個對象以 SpawnActor 為例,其接口格式如下:
AActor* UWorld::SpawnActor( UClass* Class, FVector const* Location, FRotator const* Rotation, const FActorSpawnParameters& SpawnParameters )
UClass*
參數(shù)可以通過如 AWeapon::StaticClass()
或者 TSubClassOf
獲取,核心調(diào)用鏈如下:
SpawnParameters.template
,如果不存在則使用 CDO (每個 UClass 創(chuàng)建時會有對應(yīng)描述的 Class 的 Default Object,可以認(rèn)為是調(diào)用了 Class 的默認(rèn)構(gòu)造函數(shù)構(gòu)建出來的)調(diào)用 NewObjectStaticConstructObject_InternalStaticAllocateObject檢查對象是否已經(jīng)存在不存在則調(diào)用 AllocateUObject 分配一個 UObject調(diào)用 UClass->ClassConstructor
在 UObject 上構(gòu)建對應(yīng)類返回 Actor4.2 UClass 如何獲取描述類的構(gòu)造函數(shù)4.1 中說到,UClass
是利用 ClassConstructor
來構(gòu)建對應(yīng)描述的 Class 對象的,ClassConstructor
初始化的時機(jī)在于構(gòu)建 UClass
。UClass
的構(gòu)建通過調(diào)用 TClass::StaticClass
,具體執(zhí)行流程參考 [[Pasted image 20230329232659.png|3.3.2]] 中第二步初始化 UClass。其具體初始化方式便是通過宏 DECLARE_CLASS
和 IMPLEMENT_CLASS
來生成相應(yīng)代碼并將其傳入到構(gòu)建 UClass 的一環(huán)中。
回顧類圖。
UFunction 的所有參數(shù)和返回值都存儲在父類 UStruct::PropertyLink
,這是一個鏈表結(jié)構(gòu),元素類型為 FProperty
,通過遍歷并且做標(biāo)記比對來判斷 Property 是參數(shù)還是返回值,以獲取返回值為例,其操作如下:
/** 獲取 UFunction 返回值 **/FProperty* UFunction::GetReturnProperty() const { for( TFieldIterator It(this); It && (It->PropertyFlags & CPF_Parm); ++It ) { if( It->PropertyFlags & CPF_ReturnParm ) {return *It; } } return NULL; }
4.4 UFunction 的執(zhí)行首先在 UE 中,粗分下來有兩種函數(shù):
藍(lán)圖函數(shù)C++ 函數(shù)UE 中用了一個FUNC_Native
標(biāo)記來區(qū)分,Native 函數(shù)是 C++ 函數(shù),非 Native 函數(shù)則是藍(lán)圖函數(shù)。當(dāng)執(zhí)行 UFunction 時,需要調(diào)用 UFunction::Invoke
接口。接口會調(diào)用 UFunction::Func
函數(shù)指針。當(dāng) UFunction 類型為 Native 時,F(xiàn)unc 指向?qū)嶋H調(diào)用的函數(shù),反之 Func 則指向 UObject::ProcessInternal
。藍(lán)圖函數(shù)的調(diào)用原理涉及到藍(lán)圖虛擬機(jī),在[[藍(lán)圖與 CPP 之間相互調(diào)用|藍(lán)圖篇]]做補(bǔ)充。
4.5 RPC 函數(shù)如何執(zhí)行的這里以純 C++ 實(shí)現(xiàn)武器開火為例,開火顯然是一個需要服務(wù)器認(rèn)證的 Function,為了能夠在客戶端上調(diào)用,服務(wù)器上執(zhí)行,需要加上 Server 標(biāo)記
#include "Weapon.generated.h" // 包含自動生成的頭文件信息UCLASS() // 注冊類信息class AWeapon : public AActor {GENERATED_BODY()public:UFUNCTION(Server) /* client 調(diào)用,Server 執(zhí)行 */void Fire(); /* 定義時只需要定義 Fire_Implementation */}
接著需要在 Weapon.cpp 中定義 void Fire_Implementation()
接口,此接口為服務(wù)器收到請求后執(zhí)行的接口。 在調(diào)用開火時,只需要如下操作,就可以從 client 調(diào)用到 server 的 fire 函數(shù):
AWeapon* Weapon = GetWeapon();Weapon->Fire();
這里的原理是 UHT 在對 RPC 函數(shù)會在 gen.cpp 中額外生成一個新的函數(shù)定義,格式如下:
/* gen.cpp */void AWeapon::Fire() { ProcessEvent(FindFunctionChecked(NAME_AWeapon_Fire),NULL); }
UObject::ProcessEvent
接口會調(diào)用 UObject::CallRemoteFuntion
將請求發(fā)送到服務(wù)器,服務(wù)器接受到請求后再利用反射查詢要執(zhí)行的函數(shù)名稱和對象,再對其進(jìn)行執(zhí)行。
/* gen.cpp */// 函數(shù)名稱及執(zhí)行函數(shù)關(guān)聯(lián)起來static const FNameNativePtrPair Funcs[] = {{"Fire", &AWeapon::execFire},}// 服務(wù)器執(zhí)行的函數(shù)定義DEFINE_FUNCTION(AWeapon::execFire) { P_FINISH; P_NATIVE_BEGIN; P_THIS->SpawnHero13(); P_NATIVE_END; }
其執(zhí)行流程大致如下:
Copyright © 2015-2022 華中時尚網(wǎng)版權(quán)所有 備案號:京ICP備12018864號-26 聯(lián)系郵箱:2 913 236 @qq.com