上一篇翻译的文章里面提到了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中其它模块的实现,比如整个蓝图的实现、多线程渲染、以及基于物理的渲染等内容。
由于最上面的类图尺寸过大,上传后的图片并不是特别清晰,高清原图可以在这里下载。