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 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)) }; 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
类中包含了以下信息:
存储函数的参数、返回值的结构体(POD),注意该结构的声明顺序是按照函数参数的顺序 + 最后一个成员是函数返回值的方式排列。
函数参数、返回值的F*PropertyParams
,用来给函数的每个参数以及返回值生成反射信息,用于构造出 UProperty,static 成员;
成员 static const UE4CodeGen_Private::FPropertyParamsBase* const PropPointers[];
,数组,用于记录该函数的参数和返回值的类型为FPropertyParamsBase
的 static 数据成员的地址。
成员static const UE4CodeGen_Private::FMetaDataPairParam Function_MetaDataParams[];
,用于记录函数的元数据。如所属文件、Category、注释等等。
成员 static const UE4CodeGen_Private::FFunctionParams FuncParams;
用于记录当前函数的名字、Flag、参数的F*PropertyParams
、参数数量,参数的结构大小等等,用于通过它来创建出UFunction*
。
至于生成的SetBit
d 的函数的作用,在上面属性反射的部分已经讲到了。
UE4CodeGen_Private::FFunctionParams
结构声明为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 DECLARE_FUNCTION (execReflexFunc);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 #define DECLARE_FUNCTION(func) static void func(UObject* Context, FFrame& Stack, RESULT_DECL) #define DEFINE_FUNCTION(func) void func(UObject* Context, FFrame& Stack, RESULT_DECL)
展开这两个宏:
1 2 3 4 5 6 7 8 9 10 11 12 static void AMyActor::execReflexFunc (UObject* Context, FFrame& Stack, RESULT_DECL) ;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 #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){ }
运行时访问反射函数 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
来判断是都是通过引用方式传递的“返回值”。