UE 反射实现分析:反射代码生成(一)

之前写了两篇 UE 中实现反射的文章分析,介绍了 UE 的反射基础概念和依赖的一些 C++ 特性,本篇文章开始分析 UE 反射实现的具体流程。

C++ 标准中并没有反射的特性,UE 使用的反射是基于 标记语法 UHT 扫描生成辅助代码 来实现的一套机制,正如 David Wheeler 的那句名言一样:“All problems in computer science can be solved by another level of indirection”,UHT 做的就是这样的事情,在真正执行编译之前分析标记代码并产生真正的 C++ 代码,收集反射类型的元数据,供运行时之用。

UHT 生成的代码内容很多,为了避免文章组织上的混乱,本篇文章主要讲 GENERATED_BODY/UFUNCTION 等反射标记通过 UHT 之后生成到 generated.h 中的 真正的 C++ 代码

UHT 生成的代码分别在 generated.hgen.cpp中,generated.h中的代码大多是定义了一些宏,用在所声明的类内通过编译器预处理来添加通用成员,gen.cpp中的代码则是 UHT 基于反射标记生成的用来描述类反射信息的具体代码,genrated.hgen.cpp 也是为了声明和定义分离。

UE 的 Feeds 中写过一篇关于 UE Property System 的文章:Unreal Property System(Reflection)

UE 与反射相关的 UHT 宏标记 大多定义在下列几个头文件中:

注意:不同的引擎版本,有些代码变更幅度很大,要结合具体的引擎版本做参考,重点是分析方法。

GENERATED_BODY

每一个在 UE 中继承自 UObject 的 C++ 类或者声明的 USTRUCT 类,在类声明中都会有一个 GENERATED_XXXX 的系列宏:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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)

// Include a redundant semicolon at the end of the generated code block, so that intellisense parsers can start parsing
// a new declaration if the line number/generated code is out of date.
#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()

直接看起来并没什么用!就是拼接了一个字符串而已。但是真相却往往另有玄机,搞清楚它可以顺便厘清在工作中写代码遇到的一系列会造成疑惑的问题,本节来分析一下 GENERATED_ 宏的作用。

考虑下列类代码:

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
// NetActor.h

#pragma once
#include "CoreMinimal.h"
#include "NetActor.generated.h"

UCLASS()
class ANetActor :public AActor
{
GENERATED_BODY() // 注意这个宏在 NetActor.h 的第八行
public:
UFUNCTION()
int32 GetHp()const;

UFUNCTION()
void SetHp(int32 pNewHp);
private:
UPROPERTY()
int32 mHP;
};

// NetActor.cpp
#include "NetActor.h"

int32 ANetActor::GetHp()const
{
return mHP;
}

void ANetActor::SetHp(int32 pNewHp)
{
mHP = pNewHp;
}

当我们在编译时,UBT 会驱动 UHT 为我们写的这个类生成 NetActor.generated.hNetActor.gen.cpp文件。
*.generated.h*.gen.cpp 文件存放与下列路径(相对于项目根目录):

1
Intermediate\Build\Win64\UE4Editor\Inc\{PROJECT_NAME}

其中在 NetActor.generated.h 中的代码,是 UHT 分析我们写的 NetActor.h 生成的代码 (都是宏定义,供后面使用)。
在分析 generated.h 之前需要先来说一下 GENERATED_BODYGENERATED_UCLASS_BODY宏。根据本节开头列出的 UE 所支持的一系列 GENERATED_ 宏,单纯从宏展开的角度看,GENERATED_BODYGENERATED_UCLASS_BODY 的区别就是:

1
2
3
4
# GENERATED_BODY 最终生成了这样的一串字符:
{CURRENT_FILE_ID}_{__LINE__}_GENERATED_BODY
# GENERATED_UCLASS_BODY 最终生成的是这样的一串字符串:
{CURRENT_FILE_ID}_{__LINE__}_GENERATED_BODY_LEGACY

注意:这里用 {} 括着的是其他的宏组成的,这里只是列出来两个宏的不同形式。

  • CURRENT_FILE_ID 项目所在的文件夹的名字_源文件相对路径_h
1
2
3
# e.g
# ReflectionExample\Source\ReflectionExample\NetActor.h
ReflectionExample_Source_ReflectionExample_NetActor_h
  • __LINE__为这条宏所在的文件的行数,也就是上面代码中备注说的第八行。

那么 GENERATED_BODYGENERATED_UCLASS_BODY所拼接的实际字符串就是:

1
2
3
4
// GENERATED_BODY
ReflectionExample_Source_ReflectionExample_NetActor_h_8_GENERATED_BODY
// GENERATED_UCLASS_BODY
ReflectionExample_Source_ReflectionExample_NetActor_h_8_GENERATED_BODY_LEGACY

说了这么一大堆,那么就算拼接出来了两个这么长的字符,又是干什么用的呢?

此时,打开我们的 NetActor.generated.h 文件,可以看到其中定义了一大堆宏的代码:

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
/*===========================================================================
Generated code exported from UnrealHeaderTool.
DO NOT modify this manually! Edit the corresponding .h files instead!
===========================================================================*/

#include "UObject/ObjectMacros.h"
#include "UObject/ScriptMacros.h"

PRAGMA_DISABLE_DEPRECATION_WARNINGS
#ifdef REFLECTIONEXAMPLE_NetActor_generated_h
#error "NetActor.generated.h already included, missing '#pragma once' in NetActor.h"
#endif
#define REFLECTIONEXAMPLE_NetActor_generated_h

#define ReflectionExample_Source_ReflectionExample_NetActor_h_8_RPC_WRAPPERS \
\
DECLARE_FUNCTION(execSetHp) \
{ \
P_GET_PROPERTY(UIntProperty,Z_Param_pNewHp); \
P_FINISH; \
P_NATIVE_BEGIN; \
P_THIS->SetHp(Z_Param_pNewHp); \
P_NATIVE_END; \
} \
\
DECLARE_FUNCTION(execGetHp) \
{ \
P_FINISH; \
P_NATIVE_BEGIN; \
*(int32*)Z_Param__Result=P_THIS->GetHp(); \
P_NATIVE_END; \
}

#define ReflectionExample_Source_ReflectionExample_NetActor_h_8_RPC_WRAPPERS_NO_PURE_DECLS \
\
DECLARE_FUNCTION(execSetHp) \
{ \
P_GET_PROPERTY(UIntProperty,Z_Param_pNewHp); \
P_FINISH; \
P_NATIVE_BEGIN; \
P_THIS->SetHp(Z_Param_pNewHp); \
P_NATIVE_END; \
} \
\
DECLARE_FUNCTION(execGetHp) \
{ \
P_FINISH; \
P_NATIVE_BEGIN; \
*(int32*)Z_Param__Result=P_THIS->GetHp(); \
P_NATIVE_END; \
}

#define ReflectionExample_Source_ReflectionExample_NetActor_h_8_INCLASS_NO_PURE_DECLS \
private: \
static void StaticRegisterNativesANetActor(); \
friend struct Z_Construct_UClass_ANetActor_Statics; \
public: \
DECLARE_CLASS(ANetActor, AActor, COMPILED_IN_FLAGS(CLASS_Abstract), CASTCLASS_None, TEXT("/Script/ReflectionExample"), NO_API) \
DECLARE_SERIALIZER(ANetActor)

#define ReflectionExample_Source_ReflectionExample_NetActor_h_8_INCLASS \
private: \
static void StaticRegisterNativesANetActor(); \
friend struct Z_Construct_UClass_ANetActor_Statics; \
public: \
DECLARE_CLASS(ANetActor, AActor, COMPILED_IN_FLAGS(CLASS_Abstract), CASTCLASS_None, TEXT("/Script/ReflectionExample"), NO_API) \
DECLARE_SERIALIZER(ANetActor)

#define ReflectionExample_Source_ReflectionExample_NetActor_h_8_STANDARD_CONSTRUCTORS \
/** Standard constructor, called after all reflected properties have been initialized */ \
NO_API ANetActor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); \
DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(ANetActor) \
DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, ANetActor); \
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(ANetActor); \
private: \
/** Private move- and copy-constructors, should never be used */ \
NO_API ANetActor(ANetActor&&); \
NO_API ANetActor(const ANetActor&); \
public:

#define ReflectionExample_Source_ReflectionExample_NetActor_h_8_ENHANCED_CONSTRUCTORS \
/** Standard constructor, called after all reflected properties have been initialized */ \
NO_API ANetActor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \
private: \
/** Private move- and copy-constructors, should never be used */ \
NO_API ANetActor(ANetActor&&); \
NO_API ANetActor(const ANetActor&); \
public: \
DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, ANetActor); \
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(ANetActor); \
DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(ANetActor)

#define ReflectionExample_Source_ReflectionExample_NetActor_h_8_PRIVATE_PROPERTY_OFFSET \
FORCEINLINE static uint32 __PPO__mHP() { return STRUCT_OFFSET(ANetActor, mHP); }

#define ReflectionExample_Source_ReflectionExample_NetActor_h_5_PROLOG
#define ReflectionExample_Source_ReflectionExample_NetActor_h_8_GENERATED_BODY_LEGACY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_PRIVATE_PROPERTY_OFFSET \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_RPC_WRAPPERS \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_INCLASS \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_STANDARD_CONSTRUCTORS \
public: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS

#define ReflectionExample_Source_ReflectionExample_NetActor_h_8_GENERATED_BODY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_PRIVATE_PROPERTY_OFFSET \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_RPC_WRAPPERS_NO_PURE_DECLS \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_INCLASS_NO_PURE_DECLS \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_ENHANCED_CONSTRUCTORS \
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS

template<> REFLECTIONEXAMPLE_API UClass* StaticClass<class ANetActor>();

#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID ReflectionExample_Source_ReflectionExample_NetActor_h

PRAGMA_ENABLE_DEPRECATION_WARNINGS

看到了嘛!这个生成的 generated.h 里定义了上面我们写的那些宏:

1
2
3
4
5
CURRENT_FILE_ID
// GENERATED_BODY
ReflectionExample_Source_ReflectionExample_NetActor_h_8_GENERATED_BODY
// GENERATED_UCLASS_BODY
ReflectionExample_Source_ReflectionExample_NetActor_h_8_GENERATED_BODY_LEGACY

因为我们的 NetActor.h 中包含了 NetActo.generated.h 这个头文件,所以在真正进行编译的时候会将 GENERATED_BODY 进行宏展开,展开的内容就是 NetActor.generated.h 中的宏 ReflectionExample_Source_ReflectionExample_NetActor_h_8_GENERATED_BODY 展开之后的代码。

因为我在 NetActor.h 中使用的是GENERATED_BODY,我就先分析这个宏展开之后的真实代码。

其实 GENERATED_BODYGENERATED_UCLASS_BODY的区别在于:GENERATED_BODY声明并定义 了一个接收 const FObjectInitializer& 的构造函数,GENERATED_UCLASS_BODY只声明 了该构造函数,需要用户自己提供一个定义。

1
ANetActor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

GENERATED_BODY的真实宏名字又包裹了一层其他的宏:

1
2
3
4
5
6
7
8
9
#define ReflectionExample_Source_ReflectionExample_NetActor_h_8_GENERATED_BODY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_PRIVATE_PROPERTY_OFFSET \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_RPC_WRAPPERS_NO_PURE_DECLS \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_INCLASS_NO_PURE_DECLS \
ReflectionExample_Source_ReflectionExample_NetActor_h_8_ENHANCED_CONSTRUCTORS \
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS

展开之后:

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
class ANetActor:public AActor
{
DECLARE_FUNCTION(execSetHp)
{
P_GET_PROPERTY(UIntProperty,Z_Param_pNewHp);
P_FINISH;
P_NATIVE_BEGIN;
P_THIS->SetHp(Z_Param_pNewHp);
P_NATIVE_END;
}

DECLARE_FUNCTION(execGetHp)
{
P_FINISH;
P_NATIVE_BEGIN;
*(int32*)Z_Param__Result=P_THIS->GetHp();
P_NATIVE_END;
}
private:
static void StaticRegisterNativesANetActor();
friend struct Z_Construct_UClass_ANetActor_Statics;
public:
DECLARE_CLASS(ANetActor, AActor, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/ReflectionExample"), NO_API)
DECLARE_SERIALIZER(ANetActor)

/** Standard constructor, called after all reflected properties have been initialized */
NO_API ANetActor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { };
private:
/** Private move- and copy-constructors, should never be used */
NO_API ANetActor(ANetActor&&);
NO_API ANetActor(const ANetActor&);
public:
DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, ANetActor);
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(ANetActor);
DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(ANetActor)

public:
UFUNCTION() // 注意这里的都是空宏了
int32 GetHp()const;
UFUNCTION() // 注意这里的都是空宏了
void SetHp(int32 pNewHp);
private:
UPROPERTY() // 注意这里的都是空宏了
int32 mHP;
};

可以看到其中使用了:

  • DECLARE_CLASS:声明定义当前类的几个关键信息:SuperThisClasstypedef在此处被定义,以及 StaticClass/StaticPackage/StaticClassCastFlags 和重载的 new 也被定义;
  • DECLARE_FUNCTION为使用 UFUNCIONT 标记的函数创建中间函数;
  • DECLARE_SERIALIZER:重载 << 使可以被 FArchive 序列化;
  • DECLARE_VTABLE_PTR_HELPER_CTOR:声明一个接收 FVTableHelper& 参数的构造函数;
  • DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER_DUMMY:用于 HotReload,唯一调用的地方是在Class.h 中的模板函数InternalVTableHelperCtorCaller
  • DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL:定义一个名为 __DefaultConstructor 的静态函数,其中是调用 placement-new 创建类对象(用于统一的内存分配),引擎中唯一调用的位置是在 Class.h 的模板函数InternalConstructor

因为我们没有在 ANetActor 这个类上标记 XXXX_API,所以它不会被导出,UHT 生成的类ANetActor 的构造函数中都使用的是NO_API.

还有,因为 UFUNCTION 之类的宏在 C++ 的定义里都是 空宏 ,其实严格来说他们并不能称之为,它们 只是对 UHT 的标记 ,用于通过 UHT 来解析生成.generated.h.gen.cpp的代码,所以在执行完 UHT 之后,对于 C++ 和编译器来说它们就是不存在的(在预处理之后就是彻底不存在的了)。

这几个宏 (被 UHT 生成之后就是真正的 C++ 宏了),可以在CoreUObject/Public/UObject/OBjectMacros.h 中找到定义。

把上面的宏再全部展开:

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
class ANetActor:public AActor
{
// DECLARE_FUNCTION 在本节不展开讲,留到下一节,所以我保留了宏。
DECLARE_FUNCTION(execSetHp)
{
P_GET_PROPERTY(UIntProperty,Z_Param_pNewHp);
P_FINISH;
P_NATIVE_BEGIN;
P_THIS->SetHp(Z_Param_pNewHp);
P_NATIVE_END;
}

DECLARE_FUNCTION(execGetHp)
{
P_FINISH;
P_NATIVE_BEGIN;
*(int32*)Z_Param__Result=P_THIS->GetHp();
P_NATIVE_END;
}

private:
static void StaticRegisterNativesANetActor();
friend struct Z_Construct_UClass_ANetActor_Statics;
private:
ANetActor& operator=(ANetActor&&);
ANetActor& operator=(const ANetActor&);
NO_API static UClass* GetPrivateStaticClass();

public:
// DECLARE_CLASS(ANetActor, AActor, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/ReflectionExample"), NO_API)
/** Bitwise union of #EClassFlags pertaining to this class.*/
enum {StaticClassFlags=COMPILED_IN_FLAGS(0)};
/** Typedef for the base class ({{ typedef-type }}) */
typedef AActor Super
/** Typedef for {{ typedef-type }}. */
typedef ANetActor 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/ReflectionExample");
}
/** 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;
}

// DECLARE_SERIALIZER(ANetActor)
friend FArchive &operator<<(FArchive& Ar, ANetActor*& Res)
{
return Ar << (UObject*&)Res;
}
friend void operator<<(FStructuredArchive::FSlot InSlot, ANetActor*& Res)
{
InSlot << (UObject*&)Res;
}

/** Standard constructor, called after all reflected properties have been initialized */
NO_API ANetActor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { };
private:
/** Private move- and copy-constructors, should never be used */
NO_API ANetActor(ANetActor&&);

NO_API ANetActor(const ANetActor&);
public:
// DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, ANetActor);
static UObject* __VTableCtorCaller(FVTableHelper& Helper)
{
return nullptr;
}
// DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(ANetActor);
/** DO NOT USE. This constructor is for internal usage only for hot-reload purposes. */ \
API ANetActor(FVTableHelper& Helper);

// DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(ANetActor)
static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())ANetActor(X); }

public:
UFUNCTION() // 注意这里的都是空宏了
int32 GetHp()const;
UFUNCTION() // 注意这里的都是空宏了
void SetHp(int32 pNewHp);
private:
UPROPERTY() // 注意这里的都是空宏了
int32 mHP;
};

这就是经过 UHT 之后的我们的 ANetActtor 类声明,其中定义了一系列的函数、typedef以及序列化、new等等。

还要类似于 C# 中的 Super 其实就是 UHT 给我们的类添加了一个 typedef 的形式,把 Super 定义成了基类,UE 通过这种形式给我们的类添加了通用的访问函数,用于支持 UE 的对象系统。

GetPrivateStaticClass

StaticClass 中调用的 GetPrivateStaticClass 其实现是在 NetActor.gen.cpp 中的,通过 IMPLEMENT_CLASS 宏来定义 (这个IMPLEMENT_ 系列宏也是被定义在 Class.h 中):

1
IMPLEMENT_CLASS(ANetActor, 2260007263);

展开之后为:

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
#define IMPLEMENT_CLASS(ANetActor, 2260007263)
static ANetActorCompiledInDefer<ANetActor> AutoInitializeANetActor(TEXT("ANetActor"), sizeof(ANetActor), 2260007263);
UClass* ANetActor::GetPrivateStaticClass()
{
static UClass* PrivateStaticClass = NULL;
if (!PrivateStaticClass)
{
// GetPrivateStaticClassBody is a Template function, Helper template allocate and construct a UClass
/* this could be handled with templates, but we want it external to avoid code bloat */
GetPrivateStaticClassBody(
StaticPackage(),
(TCHAR*)TEXT("ANetActor") + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0),
PrivateStaticClass,
StaticRegisterNativesANetActor,
sizeof(ANetActor),
alignof(ANetActor),
(EClassFlags)ANetActor::StaticClassFlags,
ANetActor::StaticClassCastFlags(),
ANetActor::StaticConfigName(),
(UClass::ClassConstructorType)InternalConstructor<ANetActor>,
(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<ANetActor>,
&ANetActor::AddReferencedObjects,
&ANetActor::Super::StaticClass,
&ANetActor::WithinClass::StaticClass
);
}
return PrivateStaticClass;
}

GetPrivateStaticClass(定义在 Class.cpp) 其作用是从当前类的信息构造出一个 UClass 对象出来,其是一个单例对象,通过 UXXX::StaticClass() 获取到的就是这个对象。

注意:在 GetPrivateStaticClass 中调用 GetPrivateStaticClassBody 所传递的参数,就是 UHT 根据我们类的声明产生的所有元数据的访问方法,UClass里存储的就是我们定义类的元数据,而且也并非是每一个我们定义的类都生成了一个一个 UClass 类,而是对每一个类产生一个不同的 UClass 对象实例。

UFUNCTION

在 UE 中写代码时,所有需要进行反射的函数必须添加 UFUNTION() 标记。

1
2
3
4
5
6
7
8
UCLASS()
class ANetActor:public Actor
{
GENERATED_BODY()
public:
UFUNCTION()
void SetHp(int32 pNewHp);
};

UHT 通过扫描我们在代码中所有标记了 UFUNCTION 的函数,生成出来的名为 execFUNC_NAME 中间函数定义(被称作 thunk 函数)。它统一了所有的 UFUNCTION 函数调用规则(this/ 调用参数以及返回值),并且包裹了真正要执行的函数。

之后就可以通过反射来调用该函数:

  1. 通过 UObject::FindFunction 获得所指定函数的 UFunction 对象 (如果指定的函数没有添加UFUNCTION 标记,则返回NULL);
  2. 通过 ProcessEvent 来调用函数,第一个参数是调用函数UFunction, 第二个是参数列表void*;
1
2
3
4
5
6
7
8
9
10
11
{
UFunction* funcSetHp = pNetActor->FindFunctionChecked("SetHp");
if(funcSetHp)
{
// struct define in scope
struct funcSetHpParams{int32 NewHp;}InsParam;
InsParam.NewHp=123;
// call SetHp
ProcessEvent(funcSetHp,(void*)(&InsParam));
}
}

注:UFunction对象中的 ParamSize 的大小是所有成员组成的结构大小,并且具有字节对齐,所以可以将所有参数封装为一个结构,再将其转换为void*

例,一个函数接收 int32/bool/AActor* 三个类型参数,其 ParamSize 的大小等同于:

1
2
3
4
5
6
// sizeof(Params) == 16
struct Params{
int32 pIval;
bool pBool;
AActor* pPointer;
};

内存对齐相关的内容可以看我之前的一篇文章:结构体成员内存对齐问题

DECLARE_FUNCTION

DECLARE_FUNCTION的宏定义为:

1
2
3
4
5
// This macro is used to declare a thunk function in autogenerated boilerplate code
#define DECLARE_FUNCTION(func) static void func(UObject* Context, FFrame& Stack, RESULT_DECL)

// This macro is used to define a thunk function in autogenerated boilerplate code
#define DEFINE_FUNCTION(func) void func(UObject* Context, FFrame& Stack, RESULT_DECL)

通过上面我们手动解析之后的代码可以看到,对于使用 UFUNCTION 标记的函数,UHT 解析时给我们生成了一个 DECLARE_FUNCTION 的宏,其宏定义为:

1
2
3
4
// This macro is used to declare a thunk function in autogenerated boilerplate code
#define DECLARE_FUNCTION(func) static void func(UObject* Context, FFrame& Stack, RESULT_DECL)
// This macro is used to define a thunk function in autogenerated boilerplate code
#define DEFINE_FUNCTION(func) void func(UObject* Context, FFrame& Stack, RESULT_DECL)

在我之前的文章中有提到过,C++ 的成员函数和非成员函数本质没有区别,只不过 C++ 的成员函数有一个隐式的 this 指针参数,这个 DECLARE_FUNCTION 处理的思想也一样,可以把成员和非成员函数通过这种形式统一起来,至于 Context 自然就是传统 C++ 的那个 隐式 this 指针 了,代表着当前调用该成员函数的对象。

注意:在老版本的引擎代码中(4.18.3 之前),是没有这个 Context 参数的,从 4.19 之后才支持。

Now,将上文 NetActor.generated.h 中的 DECLARE_FUNCTION(execSetHp) 展开为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// DECLARE_FUNCTION(execSetHp)
void execSetHp(UObject* Context, FFrame& Stack, RESULT_DECL)
{
// P_GET_PROPERTY(UIntProperty,Z_Param_pNewHp);
UIntProperty::TCppType Z_Param_pNewHp = UIntProperty::GetDefaultPropertyValue();
Stack.StepCompiledIn<UIntProperty>(&Z_Param_pNewHp);

// P_FINISH;
Stack.Code += !!Stack.Code; /* increment the code ptr unless it is null */

// P_NATIVE_BEGIN;
{ SCOPED_SCRIPT_NATIVE_TIMER(ScopedNativeCallTimer);

// P_THIS->SetHp(Z_Param_pNewHp);
((ThisClass*)(Context))->SetHp(Z_Param_pNewHp);

// P_NATIVE_END;
}
}

以及DECLARE_FUNCTION(execGetHp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// DECLARE_FUNCTION(execGetHp)
void execGetHp(UObject* Context, FFrame& Stack, RESULT_DECL)
{
// P_FINISH;
Stack.Code += !!Stack.Code; /* increment the code ptr unless it is null */

// P_NATIVE_BEGIN;
{ SCOPED_SCRIPT_NATIVE_TIMER(ScopedNativeCallTimer);

// *(int32*)Z_Param__Result=P_THIS->GetHp();
*(int32*)Z_Param__Result=((ThisClass*)(Context))->GetHp();

// P_NATIVE_END;
}
}

这些 P_ 开头的宏,是封装了从参数 Context 以及 Stack 中获取真正要执行的函数的参数,它们被定义在 Runtime/CoreUObject/Public/Object/ScriptMacros.h 中。

RESULT_DECL宏是被定义在 Script.h 中的:

1
2
3
4
5
6
// Runtime/CoreUObject/Public/Script.h
//
// Blueprint VM intrinsic return value declaration.
//
#define RESULT_PARAM Z_Param__Result
#define RESULT_DECL void*const RESULT_PARAM

被展开后是:

1
void*const Z_Param__Result

它是一个顶层 const(Top-level const),指针值不能修改,指针所指向的值可以修改,用于处理函数的返回值。

Custom Thunk Function

上面写道,当我们对一个函数标记 UFUNCTION 的时候,UHT 就会自动给生成一个 execFunc 的函数,如果不想让 UHT 生成该函数的 Thunk 函数,可以使用 CustomThunk 来标记,不生成,自己提供。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
UCLASS(BlueprintType,Blueprintable)
class ANetActor:public AActor
{
GENERATED_BODY()
public:
UFUNCTION(CustomThunk)
int32 GetHp()const;
DECLARE_FUNCTION(execGetHp)
{
P_FINISH;
P_NATIVE_BEGIN;
*(int32*)Z_Param__Result = P_THIS->GetHp();
P_NATIVE_END;
}
};

其实就是需要把 DECLARE_FUNCTION 自己写一遍来处理 ProcessEvent 传递过来的逻辑。比如考虑一下实现这样的逻辑:写一个通用的函数,允许传入任何具有反射的的 struct(不管是蓝图的还是 C++ 的),然后将其序列化为 json。想要实现这样的功能就需要我们在 Thunk 函数中自己来写逻辑。

1
2
3
4
5
6
7
UFUNCTION(BlueprintCallable,CustomThunk, meta = (CustomStructureParam = "StructPack"))
FString StructToJson(const FNetActorStruct& StructPack);

DECLARE_FUNCTION(execStructToJson)
{
// ...
}

metaCustomStructureParam 的含义是将参数作为通配符,可以传入任何类型的参数。

UPROPERTY

在类内对属性加了 UPROPERTY 的标记,不会在 generated.h 中产生额外的代码,但是它会把它的反射信息代码生成到在 gen.cpp 中,关于生成在 gen.cpp 中的代码细节本篇文章暂时按下不表,留到下一篇文章中详细介绍。

StaticClass/Struct/Enum

在 generated.h 中,UHT 会为当前文件中声明的反射类型(UObject class/struct/enum)生成对应的 Static*<> 模板特化:

1
2
3
template<> REFLECTIONEXAMPLE_API UClass* StaticClass<class ANetActor>();
template<> REFLECTIONEXAMPLE_API UEnum* StaticEnum<ENetEnum>();
template<> REFLECTIONEXAMPLE_API UScriptStruct* StaticStruct<struct FNetStruct>();

这是我们在运行时通过 StaticClass<ANetActor> 这种形式获取 UClass/UStruct/UEnum 的方法。

这种形式在 UE4.21 之后才添加,在 4.21 之前要使用以下这种形式:

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

Static*<>()的定义是在 gen.cpp 中。

End

UE 反射的实现以一言蔽之:通过 UHT 生成反射的元数据,把这些元数据在运行时构造出来对应的 UClass/UStruct/UEnum,从而提供了反射的支持。本篇文章主要介绍了 UHT 生成的 generated.h 中的代码,其实核心是通过 UHT 给反射类的声明中添加了一堆通用的成员,依赖这些成员支持了 UE 的对象系统的管理。

下一篇文章会着重介绍 UHT 生成的 gen.cpp 中的代码,它们是真正记录了反射类的对象信息的,比如反射成员的名字、函数地址,传递参数、数据成员类内偏移等等,通过分析它们可以知道我们通过反射能够得到类的哪些信息。