函数反射实现分析

UHT 生成的反射信息

在 UE 的代码中加了 UFUNCTION() 修饰后 UHT 就会为该函数生成反射代码。

每一个支持反射的函数 UHT 都会给它生成一个类和一个函数:
如在 AMyActor 这个类下有一个 ReflexFunc 的函数:

1
2
3
4
5
UFUNCTION()
bool ReflexFunc(int32 InIval, UObject* InObj)
{
return false;
}

UHT 会生成这样命名规则的一个类和函数:

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
// UHT 为 ReflexFunc 生成的反射代码
struct Z_Construct_UFunction_AMyActor_ReflexFunc_Statics
{
struct MyActor_eventReflexFunc_Parms
{
int32 InIval;
UObject* InObj;
bool ReturnValue;
};
static void NewProp_ReturnValue_SetBit(void* Obj);
static const UE4CodeGen_Private::FBoolPropertyParams NewProp_ReturnValue;
static const UE4CodeGen_Private::FObjectPropertyParams NewProp_InObj;
static const UE4CodeGen_Private::FIntPropertyParams NewProp_InIval;
static const UE4CodeGen_Private::FPropertyParamsBase* const PropPointers[];
#if WITH_METADATA
static const UE4CodeGen_Private::FMetaDataPairParam Function_MetaDataParams[];
#endif
static const UE4CodeGen_Private::FFunctionParams FuncParams;
};
void Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::NewProp_ReturnValue_SetBit(void* Obj)
{
((MyActor_eventReflexFunc_Parms*)Obj)->ReturnValue = 1;
}
const UE4CodeGen_Private::FBoolPropertyParams Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::NewProp_ReturnValue = {
"ReturnValue",
nullptr,
(EPropertyFlags)0x0010000000000580,
UE4CodeGen_Private::EPropertyGenFlags::Bool | UE4CodeGen_Private::EPropertyGenFlags::NativeBool,
RF_Public|RF_Transient|RF_MarkAsNative,
1,
sizeof(bool),
sizeof(MyActor_eventReflexFunc_Parms),
&Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::NewProp_ReturnValue_SetBit,
METADATA_PARAMS(nullptr,
0)
};
const UE4CodeGen_Private::FObjectPropertyParams Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::NewProp_InObj = {
"InObj",
nullptr,
(EPropertyFlags)0x0010000000000080,
UE4CodeGen_Private::EPropertyGenFlags::Object,
RF_Public|RF_Transient|RF_MarkAsNative,
1,
STRUCT_OFFSET(MyActor_eventReflexFunc_Parms,
InObj),
Z_Construct_UClass_UObject_NoRegister,
METADATA_PARAMS(nullptr,
0)
};
const UE4CodeGen_Private::FIntPropertyParams Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::NewProp_InIval = {
"InIval",
nullptr,
(EPropertyFlags)0x0010000000000080,
UE4CodeGen_Private::EPropertyGenFlags::Int,
RF_Public|RF_Transient|RF_MarkAsNative,
1,
STRUCT_OFFSET(MyActor_eventReflexFunc_Parms,
InIval),
METADATA_PARAMS(nullptr,
0)
};
const UE4CodeGen_Private::FPropertyParamsBase* const Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::PropPointers[] = {
(const UE4CodeGen_Private::FPropertyParamsBase*)&Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::NewProp_ReturnValue,
(const UE4CodeGen_Private::FPropertyParamsBase*)&Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::NewProp_InObj,
(const UE4CodeGen_Private::FPropertyParamsBase*)&Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::NewProp_InIval,
};
#if WITH_METADATA
const UE4CodeGen_Private::FMetaDataPairParam Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::Function_MetaDataParams[] = {
{ "ModuleRelativePath",
"MyActor.h" },
};
#endif
const UE4CodeGen_Private::FFunctionParams Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::FuncParams = { (UObject*(*)())Z_Construct_UClass_AMyActor,
nullptr,
"ReflexFunc",
nullptr,
nullptr,
sizeof(MyActor_eventReflexFunc_Parms),
Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::PropPointers,
UE_ARRAY_COUNT(Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::PropPointers),
RF_Public|RF_Transient|RF_MarkAsNative,
(EFunctionFlags)0x00020401,
0,
0,
METADATA_PARAMS(Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::Function_MetaDataParams,
UE_ARRAY_COUNT(Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::Function_MetaDataParams))
};

// 通过 UHT 生成的反射信息来构造出 UFunction
UFunction* Z_Construct_UFunction_AMyActor_ReflexFunc()
{
static UFunction* ReturnFunction = nullptr;
if (!ReturnFunction)
{
UE4CodeGen_Private::ConstructUFunction(ReturnFunction, Z_Construct_UFunction_AMyActor_ReflexFunc_Statics::FuncParams);
}
return ReturnFunction;
}

定义的 Z_Construct_UFunction_AMyActor_ReflexFunc_Statics 类中包含了以下信息:

  1. 存储函数的参数、返回值的结构体(POD),注意该结构的声明顺序是按照函数参数的顺序 + 最后一个成员是函数返回值的方式排列。
  2. 函数参数、返回值的F*PropertyParams,用来给函数的每个参数以及返回值生成反射信息,用于构造出 UProperty,static 成员;
  3. 成员 static const UE4CodeGen_Private::FPropertyParamsBase* const PropPointers[];,数组,用于记录该函数的参数和返回值的类型为FPropertyParamsBase 的 static 数据成员的地址。
  4. 成员static const UE4CodeGen_Private::FMetaDataPairParam Function_MetaDataParams[];,用于记录函数的元数据。如所属文件、Category、注释等等。
  5. 成员 static const UE4CodeGen_Private::FFunctionParams FuncParams; 用于记录当前函数的名字、Flag、参数的F*PropertyParams、参数数量,参数的结构大小等等,用于通过它来创建出UFunction*
  6. 至于生成的SetBitd 的函数的作用,在上面属性反射的部分已经讲到了。

UE4CodeGen_Private::FFunctionParams结构声明为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Source/Runtime/CoreUObject/Public/UObject/UObjectGlobals.h
struct FFunctionParams
{
UObject* (*OuterFunc)();
UFunction* (*SuperFunc)();
const char* NameUTF8;
const char* OwningClassName;
const char* DelegateName;
SIZE_T StructureSize;
const FPropertyParamsBase* const* PropertyArray;
int32 NumProperties;
EObjectFlags ObjectFlags;
EFunctionFlags FunctionFlags;
uint16 RPCId;
uint16 RPCResponseId;
#if WITH_METADATA
const FMetaDataPairParam* MetaDataArray;
int32 NumMetaData;
#endif
};

UHT 生成的 Z_Construct_UFunction_AMyActor_ReflexFunc 函数做了以下事情:

1
2
3
4
5
6
7
8
9
UFunction* Z_Construct_UFunction_AMyActor_ReflexFunc()
{
static UFunction* ReturnFunction = nullptr;
if (!ReturnFunction)
{
UE4CodeGen_Private::ConstructUFunction(ReturnFunction, Z_Construct_UFunction_AMyActor_Add_Statics::FuncParams);
}
return ReturnFunction;
}

根据定义的 Z_Construct_UFunction_AMyActor_ReflexFunc_Statics 结构中的 FuncParams 成员来创建出真正的 UFunction 对象。

最后,Z_Construct_UFunction_AMyActor_ReflexFunc这个函数会被注册到当前类反射数据的 FuncInfo 中。

Thunk 函数

UE 会为标记为UFUNCTION 的 Native 函数生成对应的 Thunk 函数 (BlueprintImplementableEvent 的函数不会)。如下列函数:

1
2
3
4
5
UFUNCTION()
bool ReflexFunc(int32 InIval, UObject* InObj)
{
return false;
}

生成的 Thunk 函数形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// generated.h
DECLARE_FUNCTION(execReflexFunc);

// gen.cpp
DEFINE_FUNCTION(AMyActor::execReflexFunc)
{
P_GET_PROPERTY(FIntProperty,Z_Param_InIval);
P_GET_OBJECT(UObject,Z_Param_InObj);
P_FINISH;
P_NATIVE_BEGIN;
*(bool*)Z_Param__Result=P_THIS->ReflexFunc(Z_Param_InIval,Z_Param_InObj);
P_NATIVE_END;
}

DECLARE_FUNCTION/DEFINE_FUNCTION这两个宏是定义在 CoreUObject/Public/UObject/ObjectMacros.h 中的:

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)

展开这两个宏:

1
2
3
4
5
6
7
8
9
10
11
12
// generated.h
static void AMyActor::execReflexFunc(UObject* Context, FFrame& Stack, RESULT_DECL);
// gen.cpp
void AMyActor::execReflexFunc(UObject* Context, FFrame& Stack, RESULT_DECL)
{
P_GET_PROPERTY(FIntProperty,Z_Param_InIval);
P_GET_OBJECT(UObject,Z_Param_InObj);
P_FINISH;
P_NATIVE_BEGIN;
*(bool*)Z_Param__Result=P_THIS->ReflexFunc(Z_Param_InIval,Z_Param_InObj);
P_NATIVE_END;
}

可以看到,UHT 为每个反射函数生成的都是一个参数一致的 static 成员函数,接收通用的参数,就可以用来处理所有的函数调用。

Thunk 函数中用到的这些宏:

RESULT_DECL(CoreUObject/Public/UObject/Script.h):

1
2
3
4
5
//
// Blueprint VM intrinsic return value declaration.
//
#define RESULT_PARAM Z_Param__Result
#define RESULT_DECL void*const RESULT_PARAM

其他的形如 P_GET_PROPERTY 之类的宏,都是定义在 CoreUObject/Public/UObject/ScriptMacros.h 文件中的,作用就是从栈上操作参数(因为 Thunk 函数是通用的参数,所以要从通用的参数中获取到每个函数具体的参数,UE 提供这些宏来做这些事情)。

这些 Thunk 函数通过 UHT 生成的 StaticRegisterNatives* 函数注册到 UClass 中(在 GetPrivateStaticClass 把该函数指针传递了进去):

1
2
3
4
5
6
7
8
9
void AMyActor::StaticRegisterNativesAMyActor()
{
UClass* Class = AMyActor::StaticClass();
static const FNameNativePtrPair Funcs[] = {
{ "BPNativeEvent", &AMyActor::execBPNativeEvent },
{ "ReflexFunc", &AMyActor::execReflexFunc },
};
FNativeFunctionRegistrar::RegisterFunctions(Class, Funcs, UE_ARRAY_COUNT(Funcs));
}

UE 不会为 BlueprintImplementableEvent 生成 Thunk 函数,但是会为它生成函数的反射信息,所以也可以通过反射的信息来调用 BlueprintImplementableEvent 的函数。

因为 `BlueprintImplementatableEventd 的函数是 C++ 提供原型不提供实现,让蓝图来进行覆写的,所以它不使用 Thunk 的形式调用(应该执行字节码的方式,这个暂时还没看到,有时间再来分析)。

Custom Thunk

前面讲到当给函数加了 UFUNCTION 标记时 UHT 会给我们生成对应的 Thunk 函数,但是有些情况下需要我们自己来写 Thunk 的函数,如 UKismetArrayLibrary 中的对 Array 进行操作的函数,或者 UDataTableFunctionLibrary 中的 GetDataTableRowFromName 函数。

UE 提供了让我们自己实现 Thunk 函数的方法,在 UFUNCTION 中添加 CustomThunk 标记:

1
2
UFUNCTION(CustomThunk)
bool ReflexFunc(int32 InIval, UObject* InObj)

这样 UHT 就不会为这个函数生成出它的 Thunk 函数,这种情况下就需要自己提供了。自己写的方式和 UHT 生成的代码一样,可以使用 DECLARE_FUNCTION 或者DEFINE_FUNCTION(手动写按照 Thunk 函数的签名规则也是没问题的)

1
2
3
4
DECLARE_FUNCTION(execReflexFunc)
{
// do something...
}

运行时访问反射函数

1
2
3
4
5
6
7
8
9
10
11
12
for (TFieldIterator<UFunction> It(InActor->GetClass()); It; ++It)
{
UFunction* FuncProperty = *It;
if (FuncProperty->GetName() == TEXT("GetIval"))
{
struct CallParam
{
int32 ival;
}CallParamIns;
InActor->ProcessEvent(FuncProperty, &CallParamIns);
}
}

通过 ProcessEvent 来调用,第二个参数传递进去参数和返回值的结构。

每个被反射的函数 UHT 都会给它生成一个 参数的结构体,其排列的顺序为:函数参数依次排列,最后一个成员为返回值。如:

1
2
UFUNCTION()
int32 Add(int32 R, int32 L);

UHT 为其生成的参数结构为:

1
2
3
4
5
6
struct MyActor_eventAdd_Parms
{
int32 R;
int32 L;
int32 ReturnValue;
};

在通过 UFunction* 来调用函数时,需要把这个布局的结构传递作为传递给函数的参数以及接收返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (TFieldIterator<UFunction> It(InActor->GetClass()); It; ++It)
{
UFunction* Property = *It;
if (Property->GetName() == TEXT("Add"))
{
struct AddFuncCallParam
{
int32 R;
int32 L;
int32 RetValue;
}CallParamIns;
CallParamIns.R = 123;
CallParamIns.L = 456;
InActor->ProcessEvent(Property, &CallParamIns);
UE_LOG(LogTemp, Log, TEXT("UFunction:%s value:%d"), *Property->GetName(), CallParamIns.RetValue);
}
}

可以通过 UFunction 拿到当前函数的参数的结构的大小UFunction::ParmsSize,在运行时动态访问的话可以通过这个结构大小分配出一块内存,然后用 UProperty 对这块内存进行访问,因为通过 UProperty 访问成员其实本质上也是通过该成员在类内的偏移来做的(对数据成员获取成员指针得到的是一个相对于对象基址的偏移值)。

坑点

注意:通过 UE 的 UFunction 调用并不能正确地处理引用类型,如:

1
2
UFUNCTION()
int32& Add(int32 R, int32& L);

这个函数生成的反射代码和非引用的一摸一样(对 L 参数生成的 UProperty 的 Flag 会多一个 CPF_OutParm,返回值的 UProperty 还具有CPF_ReturnParm)。
这会造成通过 UFunction* 调用传递的参数和想要获取的返回值都只是一份拷贝(因为本来调用时的参数传递到 ProcessEvent 之前都会被赋值到 UHT 创建出来的参数结构),不能再后续的流程中对得到的结果进行赋值。

而且,通过遍历 UFunction 得到的参数和返回值UProperty,其中的 Offset 值是相对于 UHT 生成的参数结构。

在获取蓝图或者 C++ 中具有多个返回值 UFunction 的时候,因为 UE 的具有多个返回值的机制是通过传递进来引用实现的,所以不能够只是通过检测 UProperty 是否具有 CPF_ReturnValue 来检测,因为包含该 flag 的 UProperty 只有一个,还需要检测 CPF_OutParam 来判断是都是通过引用方式传递的“返回值”。