深入研究虚幻4反射系统实现原理(二)
发表于2016-07-29
上一篇文章中讲解了UE4中对类(UCLASS)的反射支持,这篇文章我们还是以实例的形式来讲解虚幻4对结构体(USTRUCT)以及枚举(UENUM)的支持。
结构体
首先让我们看一下测试结构体反射支持的代码,我们用USTRUCT声明了一个结构体,告诉虚幻4 要对这个类型支持反射类型,我们向其中添加了一个float类型的值来测试程序。
1 2 3 4 5 6 7 8 9 10 11 12 | #pragma once #include "ReflectionStructTest.generated.h" USTRUCT(Blueprintable) struct FReflectionTest { GENERATED_USTRUCT_BODY() UPROPERTY(BlueprintReadWrite) float ReflectionValue; }; |
生成的.generated.h文件
点击编译后,我们得到了.generated.h文件,这段代码就是GENERATED_USTRUCT_BODY()宏展开后对应的内容,代码比较简单,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. /*=========================================================================== C++ class header boilerplate exported from UnrealHeaderTool. This is automatically generated by the tools. DO NOT modify this manually! Edit the corresponding .h files instead! ===========================================================================*/ #include "ObjectBase.h" PRAGMA_DISABLE_DEPRECATION_WARNINGS #ifdef REFLECTIONSTUDY_ReflectionStructTest_generated_h #error "ReflectionStructTest.generated.h already included, missing '#pragma once' in ReflectionStructTest.h" #endif #define REFLECTIONSTUDY_ReflectionStructTest_generated_h #define ReflectionStudy_Source_ReflectionStudy_ReflectionStructTest_h_9_GENERATED_BODY friend REFLECTIONSTUDY_API class UScriptStruct* Z_Construct_UScriptStruct_FReflectionTest(); REFLECTIONSTUDY_API static class UScriptStruct* StaticStruct(); #undef CURRENT_FILE_ID #define CURRENT_FILE_ID ReflectionStudy_Source_ReflectionStudy_ReflectionStructTest_h PRAGMA_ENABLE_DEPRECATION_WARNINGS |
它主要做了以下三件事情:
- friend REFLECTIONSTUDY_API class UScriptStruct* Z_Construct_UScriptStruct_FReflectionTest() 定义了一个友元函数,用于创建这个结构体的反射对象UScriptStruct
- REFLECTIONSTUDY_API static class UScriptStruct* StaticStruct(); 定义了一个成员函数StaticStruct(),这样我们可以通过类来获取它的反射结构体。
- #define CURRENT_FILE_ID ReflectionStudy_Source_ReflectionStudy_ReflectionStructTest_h 重新定义了CURRENT_FILE_ID 详细信息请参照开头提到的内容有对GENERATED_USTRUCT_BODY()的讲解
.generated.cpp中相关的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | UScriptStruct* Z_Construct_UScriptStruct_FReflectionTest() { UPackage* Outer = Z_Construct_UPackage__Script_ReflectionStudy(); extern uint32 Get_Z_Construct_UScriptStruct_FReflectionTest_CRC(); static UScriptStruct* ReturnStruct = FindExistingStructIfHotReloadOrDynamic(Outer, TEXT( "ReflectionTest" ), sizeof (FReflectionTest), Get_Z_Construct_UScriptStruct_FReflectionTest_CRC(), false ); if (!ReturnStruct) { ReturnStruct = new (EC_InternalUseOnlyConstructor, Outer, TEXT( "ReflectionTest" ), RF_Public|RF_Transient|RF_MarkAsNative) UScriptStruct(FObjectInitializer(), NULL, new UScriptStruct::TCppStructOps, EStructFlags(0x00000001)); UProperty* NewProp_ReflectionValue = new (EC_InternalUseOnlyConstructor, ReturnStruct, TEXT( "ReflectionValue" ), RF_Public|RF_Transient|RF_MarkAsNative) UFloatProperty(CPP_PROPERTY_BASE(ReflectionValue, FReflectionTest), 0x0010000000000004); ReturnStruct->StaticLink(); #if WITH_METADATA UMetaData* MetaData = ReturnStruct->GetOutermost()->GetMetaData(); MetaData->SetValue(ReturnStruct, TEXT( "BlueprintType" ), TEXT( "true" )); MetaData->SetValue(ReturnStruct, TEXT( "IsBlueprintBase" ), TEXT( "true" )); MetaData->SetValue(ReturnStruct, TEXT( "ModuleRelativePath" ), TEXT( "ReflectionStructTest.h" )); MetaData->SetValue(NewProp_ReflectionValue, TEXT( "Category" ), TEXT( "ReflectionTest" )); MetaData->SetValue(NewProp_ReflectionValue, TEXT( "ModuleRelativePath" ), TEXT( "ReflectionStructTest.h" )); #endif } return ReturnStruct; } uint32 Get_Z_Construct_UScriptStruct_FReflectionTest_CRC() { return 486791486U; } |
从上面的代码中我们可以得知三点:
- 创建UScriptStruct并添加到当前工程特定的package中
- 创建我们上文中添加的ReflectionValue属性
- 添加元数据,供编辑器使用,比如我们上面在USTRUCT中指定的BlueprintType
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class UScriptStruct* FReflectionTest::StaticStruct() { extern REFLECTIONSTUDY_API class UPackage* Z_Construct_UPackage__Script_ReflectionStudy(); static class UScriptStruct* Singleton = NULL; if (!Singleton) { extern REFLECTIONSTUDY_API class UScriptStruct* Z_Construct_UScriptStruct_FReflectionTest(); extern REFLECTIONSTUDY_API uint32 Get_Z_Construct_UScriptStruct_FReflectionTest_CRC(); Singleton = GetStaticStruct(Z_Construct_UScriptStruct_FReflectionTest, Z_Construct_UPackage__Script_ReflectionStudy(), TEXT( "ReflectionTest" ), sizeof (FReflectionTest), Get_Z_Construct_UScriptStruct_FReflectionTest_CRC()); } return Singleton; } static FCompiledInDeferStruct Z_CompiledInDeferStruct_UScriptStruct_FReflectionTest(FReflectionTest::StaticStruct, TEXT( "/Script/ReflectionStudy" ), TEXT( "ReflectionTest" ), false , nullptr, nullptr); |
上面的代码主要做了两件事情:
- StaticStruct()判断Singleton是否为空,如果为空,那么就调用GetStaticStruct(),面GetStaticStruct()就是调用了Z_Construct_UScriptStruct_FReflectionTest()函数。
1 2 3 4 | class UScriptStruct *GetStaticStruct( class UScriptStruct *(*InRegister)(), UObject* StructOuter, const TCHAR * StructName, SIZE_T Size, uint32 Crc) { return (*InRegister)(); } |
- 定义了一个静态全局变量,用于注册到一个列表中,在引擎初始化的时候调用StaticStruct()方法。
1 2 3 4 5 6 7 | static struct FScriptStruct_ReflectionStudy_StaticRegisterNativesFReflectionTest { FScriptStruct_ReflectionStudy_StaticRegisterNativesFReflectionTest() { UScriptStruct::DeferCppStructOps(FName(TEXT( "ReflectionTest" )), new UScriptStruct::TCppStructOps); } } ScriptStruct_ReflectionStudy_StaticRegisterNativesFReflectionTest; |
- 定义一个静态变量用于存储一个CppStructOps(主要用来 动态获取结构体的构造和析造函数) ,用于在程序中使用。
枚举
接下来我们来看下枚举的实现方式,测试代码如下所示:
1 2 3 4 5 6 | UENUM(BlueprintType) enum class EReflectionTest : uint8 { E0, E1 }; |
生成的.generated.h文件
编译代码,我们在.generated.h文件中会得到如下代码,它仅仅定义了一个FOREACH_ENUM_EREFLECTIONTEST的宏。
1 2 3 | #define FOREACH_ENUM_EREFLECTIONTEST(op) op(EReflectionTest::E0) op(EReflectionTest::E1) |
.generated.cpp中相关代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | UEnum* Z_Construct_UEnum_ReflectionStudy_EReflectionTest() { UPackage* Outer=Z_Construct_UPackage__Script_ReflectionStudy(); extern uint32 Get_Z_Construct_UEnum_ReflectionStudy_EReflectionTest_CRC(); static UEnum* ReturnEnum = FindExistingEnumIfHotReloadOrDynamic(Outer, TEXT( "EReflectionTest" ), 0, Get_Z_Construct_UEnum_ReflectionStudy_EReflectionTest_CRC(), false ); if (!ReturnEnum) { ReturnEnum = new (EC_InternalUseOnlyConstructor, Outer, TEXT( "EReflectionTest" ), RF_Public|RF_Transient|RF_MarkAsNative) UEnum(FObjectInitializer()); TArray EnumNames; EnumNames.Add(TPairInitializer(FName(TEXT( "EReflectionTest::E0" )), 0)); EnumNames.Add(TPairInitializer(FName(TEXT( "EReflectionTest::E1" )), 1)); EnumNames.Add(TPairInitializer(FName(TEXT( "EReflectionTest::EReflectionTest_MAX" )), 2)); ReturnEnum->SetEnums(EnumNames, UEnum::ECppForm::EnumClass); ReturnEnum->CppType = TEXT( "EReflectionTest" ); #if WITH_METADATA UMetaData* MetaData = ReturnEnum->GetOutermost()->GetMetaData(); MetaData->SetValue(ReturnEnum, TEXT( "BlueprintType" ), TEXT( "true" )); MetaData->SetValue(ReturnEnum, TEXT( "ModuleRelativePath" ), TEXT( "ReflectionStructTest.h" )); #endif } return ReturnEnum; } uint32 Get_Z_Construct_UEnum_ReflectionStudy_EReflectionTest_CRC() { return 1111016117U; } |
上面代码的主要作用就是:
- 查看反射的UEnum有没有生成,如果没有生成,那么会new一个UEnum并且将我们定义的E0,E1,两个枚举添加进来,而且默认都会添加一个 '枚举名+_Max'的一个枚举值
- 注册编辑器需要的元数据,比如我们在UENUM()中添加的BlueprintType
1 2 3 4 5 6 7 8 9 10 11 12 13 | static class UEnum* EReflectionTest_StaticEnum() { extern REFLECTIONSTUDY_API class UPackage* Z_Construct_UPackage__Script_ReflectionStudy(); static class UEnum* Singleton = NULL; if (!Singleton) { extern REFLECTIONSTUDY_API class UEnum* Z_Construct_UEnum_ReflectionStudy_EReflectionTest(); Singleton = GetStaticEnum(Z_Construct_UEnum_ReflectionStudy_EReflectionTest, Z_Construct_UPackage__Script_ReflectionStudy(), TEXT( "EReflectionTest" )); } return Singleton; } static FCompiledInDeferEnum Z_CompiledInDeferEnum_UEnum_EReflectionTest(EReflectionTest_StaticEnum, TEXT( "/Script/ReflectionStudy" ), TEXT( "EReflectionTest" ), false , nullptr, nullptr); |
这个代码跟上面结构的比较相似,也是主要做了两件事情:
- EReflectionTest_StaticEnum()判断Singleton是不是为空,如果为空那么通过GetStaticEnum来创建或返回UEnum对象,具体可参考GetStaticEnum实现。
- 向一个列表中注册EReflectionTest_StaticEnum()函数,用于在引擎启动的时候调用。
到这里关于虚幻4中怎样对类、结构体、函数、属性、变量支持反射有了一个简单的了解,相信读者你也有了一定的认识。但是具体怎么注册到引擎的初始化列表,以及是如何调用的,我们这里并没有展开来讲,限于篇幅问题,我们下一篇文章再来讲解着方面的内容,当然如果可以我也会把在C++中支持反射类型的几种方式也介绍一下。
作者: 风恋残雪
出处: http://www.cnblogs.com/ghl_carmack
关于作者:专注游戏引擎,关注VR,对操作系统、编译原理有深厚兴趣!
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接,否则保留追究法律责任的权利。