1. 反射
什么是反射?或者说反射能做什么,简单来说,反射可以提供一种能力,能够在运行时动态获取对象的成员信息,如成员函数,成员变量。
UE 在其反射系统上支持了许多功能,如:
- 编辑器中可供编辑的属性
- GC
- 序列化
- 网络同步
1.1 使用反射的准备工作
UE 中应用反射需要与它定义的宏相结合,主要有 3 种类型,如下所示:
- 类注册
#include "Weapon.generated.h" // 包含自动生成的头文件信息 | |
UCLASS() // 注册类信息 | |
class AWeapon : public AActor { | |
GENERATED_BODY() // 生成类辅助代码 | |
public: | |
UPROPERTY() // 注册类属性 | |
FName WeaponName; | |
UFUNCTION() // 注册类成员函数 | |
void Fire(); | |
} |
- 结构体注册(需要注意的是,
UFUNCTION
只能在 Class 中使用)
#include "Weapon.generated.h" // 包含自动生成的头文件信息 | |
USTRUCT() // 注册结构体 | |
struct FWeapon { | |
UPROPERTY() // 注册结构体属性 | |
FName WeaponName; | |
} |
- 枚举注册
#include "Weapon.generated.h" // 包含自动生成的头文件信息 | |
UENUM() // 注册枚举信息 | |
enum WeaponType { | |
Short, | |
Middle, | |
Far, | |
} |
1.2 反射的简单应用
前面注册完毕反射后,就能简单的使用反射了,如下:
#include "human.generated.h" // 包含自动生成的头文件信息 | |
/** UHuman.h **/ | |
class UHuman { | |
public: | |
UPROPERTY() | |
FString Name = "Hello, Reflection!!!"; | |
UPROPERTY() | |
UHuman* Child; | |
} | |
UHuman* Human = NewObject<UHuman>(); | |
UClass* UCHuman = UHuman::StaticClass(); | |
// 转为对应的Property | |
if (FStrProperty* StrProperty = CastField<FStrProperty>(Property)) | |
{ | |
// 取Property地址(因为属性系统知道属性在类内存中的偏移值) | |
void* PropertyAddr = StrProperty->ContainerPtrToValuePtr<void>(Human); | |
// 通过地址取值(其实就是类型转换,毕竟我们都拿到内存地址了) | |
FString PropertyValue = StrProperty->GetPropertyValue(PropertyAddr); | |
UE_LOG(LogTemp, Warning, TEXT("Property's Value is %s"), *PropertyValue); | |
} |
但是这种使用只是最粗浅的使用,更多时候反射的应用对我们来说是无感知的,如网络同步,编辑器的属性编辑等,都是建立在反射系统之上的,反射系统更多是一个基层系统,辅助构建其他高层次的系统。
2. 反射整体结构
UE 的反射系统其整体的结构如下:
总体来说,其各种结构对应收集不同类型的反射信息:
- UClass :收集类数据,描述一个类的成员变量,函数,父类等信息
- UEnum:收集枚举数据
- UScriptStruct :收集结构体数据
- UFunction:收集函数信息
以 UClass 为例,其采用FProperty
来储存所有的简单属性信息(如Bool
,Int
),而一些复合类型数据则使用UField
存储(如AActor
,TArray
)。这里需要认识到:UClass 等反射结构其本质上只是描述一个类的结构,本身与业务类无实际耦合关系,每个标记了UCLASS(...)
宏的 class 都会有一个UClass* Object
储存其反射信息。
3. 构建流程
从写代码的角度来说,我们只需要对变量,类等定义标注一个 宏,再 include 一个头文件就完事了,具体构建的过程则是由 UE 的编译工具去完成的。也就是 Unreal Build Tool(UBT) 和 Unreal Header Tool(UHT)。
接下来以前面的 class AWeapon
为例,展示其自动生成的内容和如何初始化其反射信息。
[!note]
UHT 是一个用于预处理源代码文件的工具,它可以识别UCLASS
、UFUNCTION
等宏,并通过生成额外的 C++ 代码来扩展类的功能。UHT 还可以用于生成反射信息,例如类的元数据和属性信息,以便在运行时进行蓝图交互等操作。UBT 是一个用于编译和链接 UE4 项目的构建系统。它可以自动管理项目中的依赖项,并生成可执行文件和动态链接库等二进制文件。UBT 还可以执行诸如打包、部署和测试等其他任务。
两个工具在 UE4 开发中密切相关,因为 UHT 生成的反射信息需要在 UBT 中使用,以便生成最终的可执行文件和动态链接库。因此,在构建 UE4 项目时,UBT 将首先调用 UHT 来处理源代码文件,然后使用生成的代码来编译和链接项目。
3.1 自动生成文件
在 [[原理#^644683|1.1 使用反射的准备工作]] 中,主要工作分为两步:
- 标注宏信息(如
UCLASS
,UFUNCTION
,UPROPERTY
) - 包含头文件
#include ${filename}.generated.h
这里头文件是利用 UHT 工具扫描生成的,其附带还会生成一个${filename}.gen.cpp
的源文件。这两个文件主要负责两件事情:
- 定义一个或多个辅助类(根据
UCLASS
和USTRUCT
等标注的结构数量),收集标注了宏信息的结构,该辅助类构造函数会返回一个构造好的UClass
- 定义一个
FCompileDeferInfo
静态变量,其构造函数会在启动时将辅助类的信息导入到一个全局的容器中,启动时会遍历这个容器,构建好 UClass 等反射信息。
其大致流程如下:
3.2 预生成代码
接下来分析预先生成的 generated.h 和 gen.cpp 都做了什么事情
一个 Class 需要注册反射信息时,其使用方式如下(有一个必要的前提条件为该 Class 的继承链中需要有 UObject
):
#include "Weapon.generated.h" // 包含自动生成的头文件信息 | |
UCLASS() // 注册类信息 | |
class AWeapon : public AActor { | |
GENERATED_BODY() // 生成类辅助代码 | |
public: | |
UPROPERTY() // 注册类属性 | |
FName WeaponName; | |
UFUNCTION() // 注册类成员函数 | |
void Fire(); | |
} |
可以看到其相关的宏主要有如下几个:
- UCLASS
- GENERATED_BODY
- UPROPERTY
- UFUNCTION
这里首先需要了解这些宏背后都做了什么
3.2.1 宏展开
关键的宏定义如下:
/* 将 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); | |
/* 纯标记,用给 UHT 扫描 */ | |
#define UPROPERTY(...) | |
#define UFUNCTION(...) |
以 3.2 的示例为例,展开后内容大致如下:
UdemyProject_Source_UdemyProject_AWeapon_h_3_PROLOG // 注册类信息 | |
class AWeapon : public AActor { | |
UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY // 生成类辅助代码 | |
public: | |
UPROPERTY() // 由于是标记,这里展开之后是没有特殊信息的 | |
FName WeaponName; | |
UFUNCTION() // 由于是标记,这里展开之后是没有特殊信息的 | |
void Fire(); | |
} |
可以看到展开后是一个个神秘的符号,其实这都是宏的名称,其定义在自动生成的 generated.h 文件中。
这里展示了一个特点,尽管不同的类都使用的相同的宏,但是 UHT 还是能保证扫描生成的文件信息唯一性。
这里主要关注两个宏:
- GENERATED_BODY_LEGACY
- GENERATED_BODY
接着展示一下两个宏其对应的文件信息。
#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
的内容基本一致,查阅资料发现这主要是为了前向兼容。因此可以先忽略 GENERATED_BODY_LEGACY
内容,关注 GENERATED_BODY
的内容。
可以看到 GENERATED_BODY
又嵌套了一堆宏(宏的定义在自动生成的 generated.h 头文件),其展开之后才是真正的代码,比如
/* UFUNCTION Wrapper 函数 */ | |
#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_DECLS | |
UFunction 的 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) | |
类辅助定义相关 | |
*/ | |
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; | |
} | |
/* 序列化相关 */ | |
friend FArchive &operator<<( FArchive& Ar, AWeapon*& Res ) | |
{ | |
return Ar << (UObject*&)Res; | |
} | |
friend void operator<<(FStructuredArchive::FSlot InSlot, AWeapon*& Res) \ | |
{ | |
InSlot << (UObject*&)Res; | |
} | |
/* 构造函数相关 */ | |
/** 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: | |
/* 默认构造函数 */ | |
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() // 注册类成员函数 | |
void Fire(); | |
} |
可以看到 GENERATED_BODY
宏为 AWeapon
扩展了很多功能,包括但不限于:
- 增加了构造函数
- 增加了序列化功能
- UFunction 增加 Wrapper Function 以供调用
- 增加获取当前父类以及当前类的 UClass 功能
3.2.2 gen.cpp 内容分析
gen.cpp 的内容主要为构建好描述 AWeapon
反射信息的 UClass
UFUNCTION 相关代码
首先以 AWeapon::Fire
为例,对其标记 UFUNCTION
后检查其生成的相关内容大致如下:
- 实现 Wrapper Function 的内容,这个接口主要供 蓝图 或者 RPC 使用。
DEFINE_FUNCTION(AWeapon::execFire) | |
{ | |
P_FINISH; | |
P_NATIVE_BEGIN; | |
P_THIS->Fire(); // 实际上就是调用了下 Navtive 的 Fire 函数 | |
P_NATIVE_END; | |
} |
- 生成一个结构体
FFunctionParams
,储存构建UFunction
所需的参数,并提供构建UFunction
的方法,参数内容主要分为:
- 函数的标记(比如标记为 Server 或者 Client 等)
- 函数的名称
- 函数的参数和返回值(其统一用一个 List 存储,每个元素会有一个 Flag 标记其是引用还是返回值还是普通参数)
- 参数的数量
/* 定义一个结构体,参数为构建一个 UFunction 所需要的参数 */ | |
struct Z_Construct_UFunction_AWeapon_Fire_Statics | |
{ | |
static const UE4CodeGen_Private::FFunctionParams FuncParams; | |
}; | |
/* 初始化一个结构体 */ | |
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)) }; | |
/* 生成一个构造方法,用来构造 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 相关代码
类似生成 UFunction
,此处由于 WeaponName
是基础类型,所以直接初始化一个 FNamePropertyParams
的结构体。
这里面就包含了:
- 变量的名称
- 变量的 Flag(比如标记为 Replicated)
- 变量的偏移(方便从类指针从偏移获取该变量)
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 相关代码
前面定义的函数和成员变量的代码都已经生成完毕了,接下来看具体是如何将其结合到 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, | |
}; | |
/** 成员函数 **/ | |
const FClassFunctionLinkInfo Z_Construct_UClass_AWeapon_Statics::FuncInfo[] = { | |
{ &Z_Construct_UFunction_AWeapon_Fire, "Fire" }, // 2996945510 | |
}; |
接着提供构建 AWeapon
的 UClass
信息,类似构建 UFunction
一般,其填充了一个 FClassParams
的结构体,主要内容包括但不限于:
- 成员变量列表
- 函数列表
- 类标记(即 UCLASS 宏中标记)
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)) | |
}; |
然后提供一个构建 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 小结
3.2 主要阐述自动生成的代码内容大致是什么东西,个人认为主要分为如下几点:
- 为
AWeapon
增加辅助接口(比如 Super,StaticClass,构造函数等) - 生成
AWeapon
中所有标记了UPROPERTY
和UFUNCTION
的反射代码和构建接口 - 生成
AWeapon
这个 Class 的反射代码和构建接口
最后将接口暴露出去给引擎初始化调用即可。
3.3 初始化反射信息
3.2 中预生成的代码已经封装好所有反射结构的接口了,接下来只要调用就可以生成 AWeapon
的反射信息了。
3.3.1 入口调用
UE 中反射信息主要是在引擎启动时初始化的,主要利用 gen.cpp 中自动生成的一个静态变量
static FCompiledInDefer Z_CompiledInDefer_UClass_AWeapon(Z_Construct_UClass_AWeapon, &AWeapon::StaticClass, TEXT("/Script/UdemyProject"), TEXT("AWeapon"), false, nullptr, nullptr, nullptr); |
其构造函数会将 构造 AWeapon
的 反射接口传入到一个全局容器,启动时会调用 UObjectLoadAllCompiledInDefaultProperties
遍历构造好 UClass。
大致伪代码如下:
// DeferredCompiledInRegistration 存储了 Z_Construct_UClass_AWeapon | |
static void UObjectLoadAllCompiledInDefaultProperties(){ | |
TArray<UClass* (*)()> PendingRegistrants = MoveTemp(DeferredCompiledInRegistration); | |
for (UClass* (*Registrant)() : PendingRegistrants) | |
{ | |
// 此处调用 Registrant,也就会调用 Z_Construct_UClass_AWeapon | |
UClass* Class = Registrant(); | |
/* 省略一些代码 */ | |
NewClasses.Add(Class); | |
} | |
} |
3.3.2 构建反射信息
AWeapon 反射信息的构建入口如下:
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 来描述反射信息。其具体的调用链如下(下面的 AWeapon 可替换为任意自定义的 Class):
4. QA
4.1 如何利用 UClass 构建一个对象
以 SpawnActor 为例,其接口格式如下:
AActor* UWorld::SpawnActor( UClass* Class, FVector const* Location, FRotator const* Rotation, const FActorSpawnParameters& SpawnParameters ) |
UClass*
参数可以通过如 AWeapon::StaticClass()
或者 TSubClassOf<AWeapon>()
获取,核心调用链如下:
- 准备构建参数,检查
SpawnParameters.template
,如果不存在则使用 CDO (每个 UClass 创建时会有对应描述的 Class 的 Default Object,可以认为是调用了 Class 的默认构造函数构建出来的) - 调用 NewObject
- StaticConstructObject_Internal
- StaticAllocateObject
- 检查对象是否已经存在
- 不存在则调用 AllocateUObject 分配一个 UObject
- 调用
UClass->ClassConstructor
在 UObject 上构建对应类
- 返回 Actor
4.2 UClass 如何获取描述类的构造函数
4.1 中说到,UClass
是利用 ClassConstructor
来构建对应描述的 Class 对象的,ClassConstructor
初始化的时机在于构建 UClass
。UClass
的构建通过调用 TClass::StaticClass
,具体执行流程参考 [[Pasted image 20230329232659.png|3.3.2]] 中第二步初始化 UClass。
其具体初始化方式便是通过宏 DECLARE_CLASS
和 IMPLEMENT_CLASS
来生成相应代码并将其传入到构建 UClass 的一环中。
4.3 UFunction 如何存储参数及返回值
回顾类图。
UFunction 的所有参数和返回值都存储在父类 UStruct::PropertyLink
,这是一个链表结构,元素类型为 FProperty
,通过遍历并且做标记比对来判断 Property 是参数还是返回值,以获取返回值为例,其操作如下:
/** 获取 UFunction 返回值 **/ | |
FProperty* UFunction::GetReturnProperty() const | |
{ | |
for( TFieldIterator<FProperty> It(this); It && (It->PropertyFlags & CPF_Parm); ++It ) | |
{ | |
if( It->PropertyFlags & CPF_ReturnParm ) | |
{ | |
return *It; | |
} | |
} | |
return NULL; | |
} |
4.4 UFunction 的执行
首先在 UE 中,粗分下来有两种函数:
- 蓝图函数
- C++ 函数
UE 中用了一个FUNC_Native
标记来区分,Native 函数是 C++ 函数,非 Native 函数则是蓝图函数。当执行 UFunction 时,需要调用UFunction::Invoke
接口。接口会调用UFunction::Func
函数指针。当 UFunction 类型为 Native 时,Func 指向实际调用的函数,反之 Func 则指向UObject::ProcessInternal
。
蓝图函数的调用原理涉及到蓝图虚拟机,在[[蓝图与 CPP 之间相互调用|蓝图篇]]做补充。
4.5 RPC 函数如何执行的
这里以纯 C++ 实现武器开火为例,开火显然是一个需要服务器认证的 Function,为了能够在客户端上调用,服务器上执行,需要加上 Server 标记
#include "Weapon.generated.h" // 包含自动生成的头文件信息 | |
UCLASS() // 注册类信息 | |
class AWeapon : public AActor { | |
GENERATED_BODY() | |
public: | |
UFUNCTION(Server) /* client 调用,Server 执行 */ | |
void Fire(); /* 定义时只需要定义 Fire_Implementation */ | |
} |
接着需要在 Weapon.cpp 中定义 void Fire_Implementation()
接口,此接口为服务器收到请求后执行的接口。 在调用开火时,只需要如下操作,就可以从 client 调用到 server 的 fire 函数:
AWeapon* Weapon = GetWeapon(); | |
Weapon->Fire(); |
这里的原理是 UHT 在对 RPC 函数会在 gen.cpp 中额外生成一个新的函数定义,格式如下:
/* gen.cpp */ | |
void AWeapon::Fire() | |
{ | |
ProcessEvent(FindFunctionChecked(NAME_AWeapon_Fire),NULL); | |
} |
UObject::ProcessEvent
接口会调用 UObject::CallRemoteFuntion
将请求发送到服务器,服务器接受到请求后再利用反射查询要执行的函数名称和对象,再对其进行执行。
/* gen.cpp */ | |
// 函数名称及执行函数关联起来 | |
static const FNameNativePtrPair Funcs[] = { | |
{"Fire", &AWeapon::execFire}, | |
} | |
// 服务器执行的函数定义 | |
DEFINE_FUNCTION(AWeapon::execFire) | |
{ | |
P_FINISH; | |
P_NATIVE_BEGIN; | |
P_THIS->SpawnHero13(); | |
P_NATIVE_END; | |
} |
其执行流程大致如下: