Enum 反射实现分析

UHT 为 Enum 生成的代码

在 UE 中,当我们声明一个枚举类型时可以像 UClass 一样地形式为其添加 UENUM 标记,指导 UHT 为其生成反射代码:

1
2
3
4
5
6
7
UENUM(BlueprintType)
enum class ETypeName :uint8
{
None,
Int,
Float
};

经过 UHT 之后就变成了:

1
2
3
4
5
6
7
8
// generated.h
#define FOREACH_ENUM_ETYPENAME(op) \
op(ETypeName::None) \
op(ETypeName::Int) \
op(ETypeName::Float)

enum class ETypeName : uint8;
template<> TOPDOWNEXAMPLE_API UEnum* StaticEnum<ETypeName>();
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// .gen.cpp
// End Cross Module References
static UEnum* ETypeName_StaticEnum()
{
static UEnum* Singleton = nullptr;
if (!Singleton)
{
Singleton = GetStaticEnum(Z_Construct_UEnum_TopdownExample_ETypeName, Z_Construct_UPackage__Script_TopdownExample(), TEXT("ETypeName"));
}
return Singleton;
}
template<> TOPDOWNEXAMPLE_API UEnum* StaticEnum<ETypeName>()
{
return ETypeName_StaticEnum();
}
static FCompiledInDeferEnum Z_CompiledInDeferEnum_UEnum_ETypeName(ETypeName_StaticEnum, TEXT("/Script/TopdownExample"), TEXT("ETypeName"), false, nullptr, nullptr);
uint32 Get_Z_Construct_UEnum_TopdownExample_ETypeName_Hash() { return 2221805252U; }
UEnum* Z_Construct_UEnum_TopdownExample_ETypeName()
{
#if WITH_HOT_RELOAD
UPackage* Outer = Z_Construct_UPackage__Script_TopdownExample();
static UEnum* ReturnEnum = FindExistingEnumIfHotReloadOrDynamic(Outer, TEXT("ETypeName"), 0, Get_Z_Construct_UEnum_TopdownExample_ETypeName_Hash(), false);
#else
static UEnum* ReturnEnum = nullptr;
#endif // WITH_HOT_RELOAD
if (!ReturnEnum)
{
static const UE4CodeGen_Private::FEnumeratorParam Enumerators[] = {
{ "ETypeName::None", (int64)ETypeName::None },
{ "ETypeName::Int", (int64)ETypeName::Int },
{ "ETypeName::Float", (int64)ETypeName::Float },
};
#if WITH_METADATA
const UE4CodeGen_Private::FMetaDataPairParam Enum_MetaDataParams[] = {
{ "BlueprintType", "true" },
{ "ModuleRelativePath", "MyK2Node.h" },
};
#endif
static const UE4CodeGen_Private::FEnumParams EnumParams = {
(UObject*(*)())Z_Construct_UPackage__Script_TopdownExample,
nullptr,
"ETypeName",
"ETypeName",
Enumerators,
ARRAY_COUNT(Enumerators),
RF_Public|RF_Transient|RF_MarkAsNative,
UE4CodeGen_Private::EDynamicType::NotDynamic,
(uint8)UEnum::ECppForm::EnumClass,
METADATA_PARAMS(Enum_MetaDataParams, ARRAY_COUNT(Enum_MetaDataParams))
};
UE4CodeGen_Private::ConstructUEnum(ReturnEnum, EnumParams);
}
return ReturnEnum;
}

UEnum 的构造思路和 UClass 差不多,通过 UHT 生成 Enum 的反射代码,记录枚举类型的名字、枚举值的名字、元数据等等,通过 UE4CodeGen_Private::ConstructUEnum 把这些反射数据构造出 UEnum。

同样也是通过 延迟注册 的方式把 UEnum 构造出来。

运行时访问 UEnum

如果要获取一个 UENMU 的 UEnum* 可以通过 StaticEnum<ETypeName>() 或者通过 UEnum* const MethodEnum = FindObjectChecked<UEnum>(ANY_PACKAGE, TEXT("ETypeName"), true); 来拿。

在 UE4.22+ 的版本中可以使用下列方法:

1
const UEnum* TypeEnum = StaticEnum<EnumType>();

在 4.21 及之前的版本就要麻烦一点:

1
const UEnum* TypeEnum = FindObject<UEnum>(ANY_PACKAGE, TEXT("EnumType"), true);

注意上面的 TEXT("EnumType") 其中要填想要获取的枚举类型名字。

根据枚举名字获取枚举值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// get enum value by name
{
FString EnumTypeName = TEXT("ETargetPlatform");
FString EnumName = FString::Printf(TEXT("%s::%s"),*EnumTypeName,TEXT("Int"))

UEnum* ETargetPlatformEnum = FindObject<UEnum>(ANY_PACKAGE, *EnumTypeName, true);

int32 EnumIndex = ETargetPlatformEnum->GetIndexByName(FName(*EnumName));
if (EnumIndex != INDEX_NONE)
{
UE_LOG(LogTemp, Log, TEXT("FOUND ENUM INDEX SUCCESS"));
int32 EnumValue = ETargetPlatformEnum->GetValueByIndex(EnumIndex);
ETargetPlatform CurrentEnum = (ETargetPlatform)EnumValue;
}
}

如果也想再封装一层模板类,让枚举名字也可以自动获取,则需要用得到 C++ 的 RTTI 特性:

1
2
3
4
5
6
7
8
9
10
template<typename T>
static std::string GetCPPTypeName()
{
std::string result;
std::string type_name = typeid(T).name();

std::for_each(type_name.begin(),type_name.end(),[&result](const char& character){if(!std::isdigit(character)) result.push_back(character);});

return result;
}

枚举值与字符串的互相转换

有些需要序列化枚举值的需要,虽然我们可以通过 FindObject<UEnum> 传入枚举名字拿到 UEnum*,再通过GetNameByValue 拿到名字,但是这样需要针对每个枚举都要单独写,我写了模板函数来做这个事情:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 同时支持 4.21- 和 4.21+ 版本引擎
#include <typeinfo>
#include <cctype>
#include <algorithm>

template<typename T>
static std::string GetCPPTypeName()
{
std::string result;
std::string type_name = typeid(T).name();

std::for_each(type_name.begin(),type_name.end(),[&result](const char& character){if(!std::isdigit(character)) result.push_back(character);});

return result;
}

template<typename ENUM_TYPE>
static FString GetEnumNameByValue(ENUM_TYPE InEnumValue, bool bFullName = false)
{
FString result;
{
FString TypeName;
FString ValueName;

#if ENGINE_MINOR_VERSION > 21
UEnum* FoundEnum = StaticEnum<ENUM_TYPE>();
#else
FString EnumTypeName = ANSI_TO_TCHAR(GetCPPTypeName<ENUM_TYPE>().c_str());
UEnum* FoundEnum = FindObject<UEnum>(ANY_PACKAGE, *EnumTypeName, true);
#endif
if (FoundEnum)
{
result = FoundEnum->GetNameByValue((int64)InEnumValue).ToString();
result.Split(TEXT("::"), &TypeName, &ValueName, ESearchCase::CaseSensitive, ESearchDir::FromEnd);
if (!bFullName)
{
result = ValueName;
}
}
}
return result;
}

以及从字符串获取枚举值:

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
template<typename ENUM_TYPE>
static bool GetEnumValueByName(const FString& InEnumValueName, ENUM_TYPE& OutEnumValue)
{
bool bStatus = false;

#if ENGINE_MINOR_VERSION >22
UEnum* FoundEnum = StaticEnum<ENUM_TYPE>();
FString EnumTypeName = FoundEnum->CppType;
#else
FString EnumTypeName = *GetCPPTypeName<ENUM_TYPE>();
UEnum* FoundEnum = FindObject<UEnum>(ANY_PACKAGE, *EnumTypeName, true);
#endif

if (FoundEnum)
{
FString EnumValueFullName = EnumTypeName + TEXT("::") + InEnumValueName;
int32 EnumIndex = FoundEnum->GetIndexByName(FName(*EnumValueFullName));
if (EnumIndex != INDEX_NONE)
{
int32 EnumValue = FoundEnum->GetValueByIndex(EnumIndex);
ENUM_TYPE ResultEnumValue = (ENUM_TYPE)EnumValue;
OutEnumValue = ResultEnumValue;
bStatus = false;
}
}
return bStatus;
}