1. 反射

什么是反射?或者说反射能做什么,简单来说,反射可以提供一种能力,能够在运行时动态获取对象的成员信息,如成员函数成员变量

UE 在其反射系统上支持了许多功能,如:

  • 编辑器中可供编辑的属性
  • GC
  • 序列化
  • 网络同步

1.1 使用反射的准备工作

UE 中应用反射需要与它定义的宏相结合,主要有 3 种类型,如下所示:

  • 类注册
  1. #include "Weapon.generated.h" // 包含自动生成的头文件信息
  2. UCLASS() // 注册类信息
  3. class AWeapon : public AActor {
  4. GENERATED_BODY() // 生成类辅助代码
  5. public:
  6. UPROPERTY() // 注册类属性
  7. FName WeaponName;
  8. UFUNCTION() // 注册类成员函数
  9. void Fire();
  10. }
  • 结构体注册(需要注意的是,UFUNCTION 只能在 Class 中使用)
  1. #include "Weapon.generated.h" // 包含自动生成的头文件信息
  2. USTRUCT() // 注册结构体
  3. struct FWeapon {
  4. UPROPERTY() // 注册结构体属性
  5. FName WeaponName;
  6. }
  • 枚举注册
  1. #include "Weapon.generated.h" // 包含自动生成的头文件信息
  2. UENUM() // 注册枚举信息
  3. enum WeaponType {
  4. Short,
  5. Middle,
  6. Far,
  7. }

1.2 反射的简单应用

前面注册完毕反射后,就能简单的使用反射了,如下:

  1. #include "human.generated.h" // 包含自动生成的头文件信息
  2. /** UHuman.h **/
  3. class UHuman {
  4. public:
  5. UPROPERTY()
  6. FString Name = "Hello, Reflection!!!";
  7. UPROPERTY()
  8. UHuman* Child;
  9. }
  10. UHuman* Human = NewObject<UHuman>();
  11. UClass* UCHuman = UHuman::StaticClass();
  12. // 转为对应的Property
  13. if (FStrProperty* StrProperty = CastField<FStrProperty>(Property))
  14. {
  15. // 取Property地址(因为属性系统知道属性在类内存中的偏移值)
  16. void* PropertyAddr = StrProperty->ContainerPtrToValuePtr<void>(Human);
  17. // 通过地址取值(其实就是类型转换,毕竟我们都拿到内存地址了)
  18. FString PropertyValue = StrProperty->GetPropertyValue(PropertyAddr);
  19. UE_LOG(LogTemp, Warning, TEXT("Property's Value is %s"), *PropertyValue);
  20. }

但是这种使用只是最粗浅的使用,更多时候反射的应用对我们来说是无感知的,如网络同步,编辑器的属性编辑等,都是建立在反射系统之上的,反射系统更多是一个基层系统,辅助构建其他高层次的系统。

2. 反射整体结构

UE 的反射系统其整体的结构如下:

总体来说,其各种结构对应收集不同类型的反射信息:

  • UClass :收集类数据,描述一个类的成员变量,函数,父类等信息
  • UEnum:收集枚举数据
  • UScriptStruct :收集结构体数据
  • UFunction:收集函数信息

    以 UClass 为例,其采用 FProperty 来储存所有的简单属性信息(如 BoolInt),而一些复合类型数据则使用 UField 存储(如 AActorTArray)。这里需要认识到:UClass 等反射结构其本质上只是描述一个类的结构,本身与业务类无实际耦合关系,每个标记了 UCLASS(...) 宏的 class 都会有一个 UClass* Object 储存其反射信息。

3. 构建流程

从写代码的角度来说,我们只需要对变量,类等定义标注一个 宏,再 include 一个头文件就完事了,具体构建的过程则是由 UE 的编译工具去完成的。也就是 Unreal Build Tool(UBT) 和 Unreal Header Tool(UHT)。

接下来以前面的 class AWeapon 为例,展示其自动生成的内容和如何初始化其反射信息。

[!note]

UHT 是一个用于预处理源代码文件的工具,它可以识别 UCLASSUFUNCTION 等宏,并通过生成额外的 C++ 代码来扩展类的功能。UHT 还可以用于生成反射信息,例如类的元数据和属性信息,以便在运行时进行蓝图交互等操作。

UBT 是一个用于编译和链接 UE4 项目的构建系统。它可以自动管理项目中的依赖项,并生成可执行文件和动态链接库等二进制文件。UBT 还可以执行诸如打包、部署和测试等其他任务。

两个工具在 UE4 开发中密切相关,因为 UHT 生成的反射信息需要在 UBT 中使用,以便生成最终的可执行文件和动态链接库。因此,在构建 UE4 项目时,UBT 将首先调用 UHT 来处理源代码文件,然后使用生成的代码来编译和链接项目。

3.1 自动生成文件

在 [[原理#^644683|1.1 使用反射的准备工作]] 中,主要工作分为两步:

  • 标注宏信息(如 UCLASSUFUNCTIONUPROPERTY
  • 包含头文件 #include ${filename}.generated.h

    这里头文件是利用 UHT 工具扫描生成的,其附带还会生成一个 ${filename}.gen.cpp 的源文件。这两个文件主要负责两件事情:
  1. 定义一个或多个辅助类(根据 UCLASSUSTRUCT 等标注的结构数量),收集标注了宏信息的结构,该辅助类构造函数会返回一个构造好的 UClass
  2. 定义一个 FCompileDeferInfo 静态变量,其构造函数会在启动时将辅助类的信息导入到一个全局的容器中,启动时会遍历这个容器,构建好 UClass 等反射信息。

    其大致流程如下:

3.2 预生成代码

接下来分析预先生成的 generated.h 和 gen.cpp 都做了什么事情

一个 Class 需要注册反射信息时,其使用方式如下(有一个必要的前提条件为该 Class 的继承链中需要有 UObject):

  1. #include "Weapon.generated.h" // 包含自动生成的头文件信息
  2. UCLASS() // 注册类信息
  3. class AWeapon : public AActor {
  4. GENERATED_BODY() // 生成类辅助代码
  5. public:
  6. UPROPERTY() // 注册类属性
  7. FName WeaponName;
  8. UFUNCTION() // 注册类成员函数
  9. void Fire();
  10. }

可以看到其相关的宏主要有如下几个:

  • UCLASS
  • GENERATED_BODY
  • UPROPERTY
  • UFUNCTION

    这里首先需要了解这些宏背后都做了什么

3.2.1 宏展开

关键的宏定义如下:

  1. /* 将 ABCD 4 个名称链接起来*/
  2. #define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
  3. #define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)
  4. /* 拼接成另一个宏 */
  5. #define UCLASS(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_PROLOG)
  6. #define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY);
  7. /* 纯标记,用给 UHT 扫描 */
  8. #define UPROPERTY(...)
  9. #define UFUNCTION(...)

以 3.2 的示例为例,展开后内容大致如下:

  1. UdemyProject_Source_UdemyProject_AWeapon_h_3_PROLOG // 注册类信息
  2. class AWeapon : public AActor {
  3. UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY // 生成类辅助代码
  4. public:
  5. UPROPERTY() // 由于是标记,这里展开之后是没有特殊信息的
  6. FName WeaponName;
  7. UFUNCTION() // 由于是标记,这里展开之后是没有特殊信息的
  8. void Fire();
  9. }

可以看到展开后是一个个神秘的符号,其实这都是宏的名称,其定义在自动生成的 generated.h 文件中。

这里展示了一个特点,尽管不同的类都使用的相同的宏,但是 UHT 还是能保证扫描生成的文件信息唯一性。

这里主要关注两个宏:

  • GENERATED_BODY_LEGACY
  • GENERATED_BODY

    接着展示一下两个宏其对应的文件信息。
  1. #define UdemyProject_Source_UdemyProject_AWeapon_h_3_PROLOG
  2. #define UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY_LEGACY \
  3. PRAGMA_DISABLE_DEPRECATION_WARNINGS \
  4. public: \
  5. UdemyProject_Source_UdemyProject_AWeapon_h_5_PRIVATE_PROPERTY_OFFSET \
  6. UdemyProject_Source_UdemyProject_AWeapon_h_5_SPARSE_DATA \
  7. UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS \
  8. UdemyProject_Source_UdemyProject_AWeapon_h_5_INCLASS \
  9. UdemyProject_Source_UdemyProject_AWeapon_h_5_STANDARD_CONSTRUCTORS \
  10. public: \
  11. #define UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY \
  12. PRAGMA_DISABLE_DEPRECATION_WARNINGS \
  13. public: \
  14. UdemyProject_Source_UdemyProject_AWeapon_h_5_PRIVATE_PROPERTY_OFFSET \
  15. UdemyProject_Source_UdemyProject_AWeapon_h_5_SPARSE_DATA \
  16. UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS_NO_PURE_DECLS \
  17. UdemyProject_Source_UdemyProject_AWeapon_h_5_INCLASS_NO_PURE_DECLS \
  18. UdemyProject_Source_UdemyProject_AWeapon_h_5_ENHANCED_CONSTRUCTORS \
  19. private: \

可以看到 GENERATED_BODY_LEGACYGENERATED_BODY 的内容基本一致,查阅资料发现这主要是为了前向兼容。因此可以先忽略 GENERATED_BODY_LEGACY 内容,关注 GENERATED_BODY 的内容。

可以看到 GENERATED_BODY 又嵌套了一堆宏(宏的定义在自动生成的 generated.h 头文件),其展开之后才是真正的代码,比如

  1. /* UFUNCTION Wrapper 函数 */
  2. #define UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS_NO_PURE_DECLS \
  3. DECLARE_FUNCTION(execFire);

可以对其完整展开,还原其最终的样貌

  1. /* 该宏可以忽略 */
  2. UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY_LEGACY
  3. class AWeapon : public AActor {
  4. public:
  5. /*
  6. UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS_NO_PURE_DECLS
  7. UFunction 的 Wrapper Function 集合
  8. */
  9. static void execFire( UObject* Context, FFrame& Stack, RESULT_DECL );
  10. private:
  11. static void StaticRegisterNativesAWeapon();
  12. friend struct Z_Construct_UClass_AWeapon_Statics;
  13. public:
  14. /*
  15. DECLARE_CLASS(AWeapon, AActor, COMPILED_IN_FLAGS(0 | CLASS_Config), CASTCLASS_None, TEXT("/Script/UdemyProject"), NO_API)
  16. 类辅助定义相关
  17. */
  18. private: \
  19. AWeapon& operator=(AWeapon&&); \
  20. AWeapon& operator=(const AWeapon&); \
  21. TRequiredAPI static UClass* GetPrivateStaticClass(); \
  22. public: \
  23. /** Bitwise union of #EClassFlags pertaining to this class.*/ \
  24. enum {StaticClassFlags=COMPILED_IN_FLAGS(0 | CLASS_Config}; \
  25. /** Typedef for the base class ({{ typedef-type }}) */ \
  26. typedef AActor Super;\
  27. /** Typedef for {{ typedef-type }}. */ \
  28. typedef AWeapon ThisClass;\
  29. /** Returns a UClass object representing this class at runtime */ \
  30. inline static UClass* StaticClass() \
  31. { \
  32. return GetPrivateStaticClass(); \
  33. } \
  34. /** Returns the package this class belongs in */ \
  35. inline static const TCHAR* StaticPackage() \
  36. { \
  37. return TEXT("/Script/UdemyProject"); \
  38. } \
  39. /** Returns the static cast flags for this class */ \
  40. inline static EClassCastFlags StaticClassCastFlags() \
  41. { \
  42. return CASTCLASS_None;
  43. }
  44. /** For internal use only; use StaticConstructObject() to create new objects. */
  45. inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags)
  46. {
  47. return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags);
  48. }
  49. /** For internal use only; use StaticConstructObject() to create new objects. */
  50. inline void* operator new( const size_t InSize, EInternal* InMem ) \
  51. {
  52. return (void*)InMem;
  53. }
  54. /* 序列化相关 */
  55. friend FArchive &operator<<( FArchive& Ar, AWeapon*& Res )
  56. {
  57. return Ar << (UObject*&)Res;
  58. }
  59. friend void operator<<(FStructuredArchive::FSlot InSlot, AWeapon*& Res) \
  60. {
  61. InSlot << (UObject*&)Res;
  62. }
  63. /* 构造函数相关 */
  64. /** Standard constructor, called after all reflected properties have been initialized */
  65. NO_API AWeapon(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { };
  66. private:
  67. /** Private move- and copy-constructors, should never be used */
  68. NO_API AWeapon(AWeapon&&);
  69. NO_API AWeapon(const AWeapon&);
  70. public:
  71. /* 默认构造函数 */
  72. NO_API AWeapon(FVTableHelper& Helper);
  73. static UObject* __VTableCtorCaller(FVTableHelper& Helper)
  74. {
  75. return new (EC_InternalUseOnlyConstructor, (UObject*)GetTransientPackage(), NAME_None, RF_NeedLoad | RF_ClassDefaultObject | RF_TagGarbageTemp) AWeapon(Helper);
  76. }
  77. static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())AWeapon(X); }
  78. public:
  79. UPROPERTY() // 注册类属性
  80. FName WeaponName;
  81. UFUNCTION() // 注册类成员函数
  82. void Fire();
  83. }

可以看到 GENERATED_BODY 宏为 AWeapon 扩展了很多功能,包括但不限于:

  • 增加了构造函数
  • 增加了序列化功能
  • UFunction 增加 Wrapper Function 以供调用
  • 增加获取当前父类以及当前类的 UClass 功能

3.2.2 gen.cpp 内容分析

gen.cpp 的内容主要为构建好描述 AWeapon 反射信息的 UClass

UFUNCTION 相关代码

首先以 AWeapon::Fire 为例,对其标记 UFUNCTION 后检查其生成的相关内容大致如下:

  1. 实现 Wrapper Function 的内容,这个接口主要供 蓝图 或者 RPC 使用。
  1. DEFINE_FUNCTION(AWeapon::execFire)
  2. {
  3. P_FINISH;
  4. P_NATIVE_BEGIN;
  5. P_THIS->Fire(); // 实际上就是调用了下 Navtive 的 Fire 函数
  6. P_NATIVE_END;
  7. }
  1. 生成一个结构体 FFunctionParams,储存构建 UFunction 所需的参数,并提供构建 UFunction 的方法,参数内容主要分为:
  • 函数的标记(比如标记为 Server 或者 Client 等)
  • 函数的名称
  • 函数的参数和返回值(其统一用一个 List 存储,每个元素会有一个 Flag 标记其是引用还是返回值还是普通参数)
  • 参数的数量
  1. /* 定义一个结构体,参数为构建一个 UFunction 所需要的参数 */
  2. struct Z_Construct_UFunction_AWeapon_Fire_Statics
  3. {
  4. static const UE4CodeGen_Private::FFunctionParams FuncParams;
  5. };
  6. /* 初始化一个结构体 */
  7. const UE4CodeGen_Private::FFunctionParams Z_Construct_UFunction_AWeapon_Fire_Statics::FuncParams =
  8. { (UObject*(*)())Z_Construct_UClass_AWeapon,
  9. nullptr,
  10. "Fire",
  11. nullptr,
  12. nullptr,
  13. 0,
  14. nullptr,
  15. 0,
  16. RF_Public|RF_Transient|RF_MarkAsNative,
  17. (EFunctionFlags)0x00020401,
  18. 0,
  19. 0, METADATA_PARAMS(Z_Construct_UFunction_AWeapon_Fire_Statics::Function_MetaDataParams, UE_ARRAY_COUNT(Z_Construct_UFunction_AWeapon_Fire_Statics::Function_MetaDataParams)) };
  20. /* 生成一个构造方法,用来构造 AWeapon::Fire 的 UFunction 信息 */
  21. UFunction* Z_Construct_UFunction_AWeapon_Fire()
  22. {
  23. static UFunction* ReturnFunction = nullptr;
  24. if (!ReturnFunction)
  25. { UE4CodeGen_Private::ConstructUFunction(ReturnFunction, Z_Construct_UFunction_AWeapon_Fire_Statics::FuncParams);
  26. } return ReturnFunction;
  27. }

UPROPERTY 相关代码

类似生成 UFunction,此处由于 WeaponName 是基础类型,所以直接初始化一个 FNamePropertyParams 的结构体。

这里面就包含了:

  • 变量的名称
  • 变量的 Flag(比如标记为 Replicated)
  • 变量的偏移(方便从类指针从偏移获取该变量)
  1. const UE4CodeGen_Private::FNamePropertyParams Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName = {
  2. "WeaponName",
  3. nullptr,
  4. (EPropertyFlags)0x0010000000000000,
  5. UE4CodeGen_Private::EPropertyGenFlags::Name,
  6. RF_Public|RF_Transient|RF_MarkAsNative,
  7. 1,
  8. STRUCT_OFFSET(AWeapon, WeaponName),
  9. METADATA_PARAMS(Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName_MetaData,
  10. UE_ARRAY_COUNT(Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName_MetaData)) };

UCLASS 相关代码

前面定义的函数和成员变量的代码都已经生成完毕了,接下来看具体是如何将其结合到 Class 中的。

首先 gen.cpp 中会生成代码将 Function 和 Property 分开存储,定义如下:

  1. /** 成员变量 **/
  2. const UE4CodeGen_Private::FPropertyParamsBase* const Z_Construct_UClass_AWeapon_Statics::PropPointers[] = {
  3. (const UE4CodeGen_Private::FPropertyParamsBase*)&Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName,
  4. };
  5. /** 成员函数 **/
  6. const FClassFunctionLinkInfo Z_Construct_UClass_AWeapon_Statics::FuncInfo[] = {
  7. { &Z_Construct_UFunction_AWeapon_Fire, "Fire" }, // 2996945510
  8. };

接着提供构建 AWeaponUClass 信息,类似构建 UFunction 一般,其填充了一个 FClassParams 的结构体,主要内容包括但不限于:

  • 成员变量列表
  • 函数列表
  • 类标记(即 UCLASS 宏中标记)
  1. const UE4CodeGen_Private::FClassParams Z_Construct_UClass_AWeapon_Statics::ClassParams = {
  2. &AWeapon::StaticClass,
  3. "Engine",
  4. &StaticCppClassTypeInfo,
  5. DependentSingletons,
  6. FuncInfo,
  7. Z_Construct_UClass_AWeapon_Statics::PropPointers,
  8. nullptr,
  9. UE_ARRAY_COUNT(DependentSingletons),
  10. UE_ARRAY_COUNT(FuncInfo),
  11. UE_ARRAY_COUNT(Z_Construct_UClass_AWeapon_Statics::PropPointers),
  12. 0,
  13. 0x008000A4u,
  14. METADATA_PARAMS(Z_Construct_UClass_AWeapon_Statics::Class_MetaDataParams, UE_ARRAY_COUNT(Z_Construct_UClass_AWeapon_Statics::Class_MetaDataParams))
  15. };

然后提供一个构建 UClass 的接口

  1. UClass* Z_Construct_UClass_AWeapon()
  2. {
  3. static UClass* OuterClass = nullptr;
  4. if (!OuterClass)
  5. { UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_AWeapon_Statics::ClassParams);
  6. } return OuterClass;
  7. }

至此,整个类的自动生成的反射代码基本描述完了。

3.2.3 小结

3.2 主要阐述自动生成的代码内容大致是什么东西,个人认为主要分为如下几点:

  • AWeapon 增加辅助接口(比如 Super,StaticClass,构造函数等)
  • 生成 AWeapon 中所有标记了 UPROPERTYUFUNCTION 的反射代码和构建接口
  • 生成 AWeapon 这个 Class 的反射代码和构建接口

    最后将接口暴露出去给引擎初始化调用即可。

3.3 初始化反射信息

3.2 中预生成的代码已经封装好所有反射结构的接口了,接下来只要调用就可以生成 AWeapon 的反射信息了。

3.3.1 入口调用

UE 中反射信息主要是在引擎启动时初始化的,主要利用 gen.cpp 中自动生成的一个静态变量

  1. static FCompiledInDefer Z_CompiledInDefer_UClass_AWeapon(Z_Construct_UClass_AWeapon, &AWeapon::StaticClass, TEXT("/Script/UdemyProject"), TEXT("AWeapon"), false, nullptr, nullptr, nullptr);

其构造函数会将 构造 AWeapon 的 反射接口传入到一个全局容器,启动时会调用 UObjectLoadAllCompiledInDefaultProperties 遍历构造好 UClass。

大致伪代码如下:

  1. // DeferredCompiledInRegistration 存储了 Z_Construct_UClass_AWeapon
  2. static void UObjectLoadAllCompiledInDefaultProperties(){
  3. TArray<UClass* (*)()> PendingRegistrants = MoveTemp(DeferredCompiledInRegistration);
  4. for (UClass* (*Registrant)() : PendingRegistrants)
  5. {
  6. // 此处调用 Registrant,也就会调用 Z_Construct_UClass_AWeapon
  7. UClass* Class = Registrant();
  8. /* 省略一些代码 */
  9. NewClasses.Add(Class);
  10. }
  11. }

3.3.2 构建反射信息

AWeapon 反射信息的构建入口如下:

  1. UClass* Z_Construct_UClass_AWeapon()
  2. {
  3. static UClass* OuterClass = nullptr;
  4. if (!OuterClass)
  5. {
  6. UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_AWeapon_Statics::ClassParams);
  7. }
  8. return OuterClass;
  9. }

即使有多个 AWeapon 对象也是共用一个 UClass 来描述反射信息。其具体的调用链如下(下面的 AWeapon 可替换为任意自定义的 Class):

4. QA

4.1 如何利用 UClass 构建一个对象

以 SpawnActor 为例,其接口格式如下:

  1. 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 初始化的时机在于构建 UClassUClass 的构建通过调用 TClass::StaticClass ,具体执行流程参考 [[Pasted image 20230329232659.png|3.3.2]] 中第二步初始化 UClass。

其具体初始化方式便是通过宏 DECLARE_CLASSIMPLEMENT_CLASS 来生成相应代码并将其传入到构建 UClass 的一环中。

4.3 UFunction 如何存储参数及返回值

回顾类图。

UFunction 的所有参数和返回值都存储在父类 UStruct::PropertyLink,这是一个链表结构,元素类型为 FProperty,通过遍历并且做标记比对来判断 Property 是参数还是返回值,以获取返回值为例,其操作如下:

  1. /** 获取 UFunction 返回值 **/
  2. FProperty* UFunction::GetReturnProperty() const
  3. {
  4. for( TFieldIterator<FProperty> It(this); It && (It->PropertyFlags & CPF_Parm); ++It )
  5. {
  6. if( It->PropertyFlags & CPF_ReturnParm )
  7. {
  8. return *It;
  9. }
  10. }
  11. return NULL;
  12. }

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 标记

  1. #include "Weapon.generated.h" // 包含自动生成的头文件信息
  2. UCLASS() // 注册类信息
  3. class AWeapon : public AActor {
  4. GENERATED_BODY()
  5. public:
  6. UFUNCTION(Server) /* client 调用,Server 执行 */
  7. void Fire(); /* 定义时只需要定义 Fire_Implementation */
  8. }

接着需要在 Weapon.cpp 中定义 void Fire_Implementation() 接口,此接口为服务器收到请求后执行的接口。 在调用开火时,只需要如下操作,就可以从 client 调用到 server 的 fire 函数:

  1. AWeapon* Weapon = GetWeapon();
  2. Weapon->Fire();

这里的原理是 UHT 在对 RPC 函数会在 gen.cpp 中额外生成一个新的函数定义,格式如下:

  1. /* gen.cpp */
  2. void AWeapon::Fire()
  3. {
  4. ProcessEvent(FindFunctionChecked(NAME_AWeapon_Fire),NULL);
  5. }

UObject::ProcessEvent 接口会调用 UObject::CallRemoteFuntion 将请求发送到服务器,服务器接受到请求后再利用反射查询要执行的函数名称和对象,再对其进行执行。

  1. /* gen.cpp */
  2. // 函数名称及执行函数关联起来
  3. static const FNameNativePtrPair Funcs[] = {
  4. {"Fire", &AWeapon::execFire},
  5. }
  6. // 服务器执行的函数定义
  7. DEFINE_FUNCTION(AWeapon::execFire)
  8. {
  9. P_FINISH;
  10. P_NATIVE_BEGIN;
  11. P_THIS->SpawnHero13();
  12. P_NATIVE_END;
  13. }

其执行流程大致如下:

UnrealEngine - 反射系统分析的更多相关文章

  1. 隐私泄露杀手锏 —— Flash 权限反射

    [简版:http://weibo.com/p/1001603881940380956046] 前言 一直以为该风险早已被重视,但最近无意中发现,仍有不少网站存在该缺陷,其中不乏一些常用的邮箱.社交网站 ...

  2. Java学习之反射机制及应用场景

    前言: 最近公司正在进行业务组件化进程,其中的路由实现用到了Java的反射机制,既然用到了就想着好好学习总结一下,其实无论是之前的EventBus 2.x版本还是Retrofit.早期的View注解框 ...

  3. 关于 CSS 反射倒影的研究思考

    原文地址:https://css-tricks.com/state-css-reflections 译者:nzbin 友情提示:由于演示 demo 的兼容性,推荐火狐浏览.该文章篇幅较长,内容庞杂,有 ...

  4. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

  5. 运用Mono.Cecil 反射读取.NET程序集元数据

    CLR自带的反射机智和API可以很轻松的读取.NET程序集信息,但是不能对程序集进行修改.CLR提供的是只读的API,但是开源项目Mono.Cecil不仅仅可以读取.NET程序集的元数据,还可以进行修 ...

  6. .NET面试题系列[6] - 反射

    反射 - 定义,实例与优化 在面试中,通常会考察反射的定义(操作元数据),可以用反射做什么(获得程序集及其各个部件),反射有什么使用场景(ORM,序列化,反序列化,值类型比较等).如果答得好,还可能会 ...

  7. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  8. C++的性能C#的产能?! - .Net Native 系列五:.Net Native与反射

    此系列系小九的学堂原创翻译,翻译自微软官方开发向导,一共分为六个主题.本文是第五个主题:.Net Native与反射. 向导文链接:<C++的性能C#的产能?! - .Net Native 系列 ...

  9. [源码]Literacy 快速反射读写对象属性,字段

    Literacy 说明 Literacy使用IL指令生成方法委托,性能方面,在调用次数达到一定量的时候比反射高很多 当然,用IL指令生成一个方法也是有时间消耗的,所以在只使用一次或少数几次的情况,不但 ...

  10. SI与EMI(一) - 反射是怎样影响EMI

    Mark为期两天的EMC培训中大概分成四个时间差不多的部分,简单来说分别是SI.PI.回流.屏蔽.而在信号完整性的书籍中,也会把信号完整性分为:1.信号自身传输的问题(反射,损耗):2.信号与信号之间 ...

随机推荐

  1. Unity中实现在规定时间内从一个值递增到另一个值

    1.进度条(在规定时间内完成进度条) private Image progressBar; private float currentProgress = 0; /// <summary> ...

  2. 【jinja2】Python根据模板生成HTML文件并加载进QWebEngineView

    前言 继前文Python在PyQt5中使用ECharts绘制图表中在Python程序中添加网页展示ECharts图表,和Python使用QWebEngineView时报错Uncaught Refere ...

  3. SQL Server 错误:特殊符号“•”导致的sql查询问题

    问题描述: 对于一些标题或字符串,例如: 如果导入数据库,就会发现会自动变成?号了: 在进行SQL查询的时候,会出现一个同一条sql语句在mysql直接执行sql可以查询到,但是mssql进行查询的时 ...

  4. SQL Server 还原数据库

    1.备份要还原的数据库 选择要备份的数据库,右键单击,任务--备份. 2.备份完成后,将数据库还原 3.新建一个空的数据库,比如Gsy_TestNew,将备份的数据库还原到这个新的库上 4.右键单击[ ...

  5. LocalDateTime接收前端String格式

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; 实体类属性添加注解,前 ...

  6. vue多图片上传组件

    <template> <!-- 上传控件 用法: <upload-widget v-model="imgUrl"></upload-widget ...

  7. 第四天 while 嵌套循环语句

    python全栈开发笔记第四天 while 嵌套循环语句 while 条件 while 条件 print() print() 例题:num1 =int(input("num1:") ...

  8. delphi 给 Timage 加上滚动条方法

    delphi Timage 加上滚动条的方法 1:将  Timage 放入 TScrollBox内,即   [1]设image1.parent:= ScrollBox1;   [2]在Object I ...

  9. 剑指Offer2---------替换空格

    题目描述 请实现一个函数,将一个字符串中的每个空格替换成"%20".例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy.   重点: ...

  10. windows server 2012 AD域服务器的搭建安装 子域的创建加入 客户机加入域环境(Active Directory域)

    1,安装Active Directory域前的准备工作 2,安装Active Directory域 3,加入子域(可选) 4,加入客户机 ******************************* ...