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中的代码,它们是真正记录了反射类的对象信息的,比如反射成员的名字、函数地址,传递参数、数据成员类内偏移等等,通过分析它们可以知道我们通过反射能够得到类的哪些信息。

全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题:UE反射实现分析:反射代码生成(一)
文章作者:查利鹏
发布时间:2021年03月10日 15时14分
本文字数:本文一共有5.5k字
原始链接:https://imzlp.com/posts/9780/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!