在计算机编程中,Thunk 是一个特殊的函数,其主要目的是延迟计算或调用,将一个多参数函数转换为一个单参数的延迟执行版本
在 Unreal 中 Thunk 用于将蓝图中的事件或者函数调用转换为 C++ 中的函数调用。 Thunk 函数充当中间层,确保蓝图和 C++ 之间的通信和数据传递能够正常运行
CustomThunk 顾名思义就是自定义 Thunk 函数,用于告诉引擎这个函数不是自动生成的,而是开发者提供的
这种自定义的 Thunk 函数允许开发者直接与蓝图虚拟机的堆栈进行交互,从而可以处理更复杂的逻辑,比如动态参数类型的处理、泛型编程或者在运行时进行特定的内存操作
CustomThunk 定义的函数,通常需要通过 DECLARE_FUNCTION 宏中以 exec 前缀来声明。这个 exec 是需要遵守的约定,因为 Unreal 的蓝图虚拟机在处理函数调用时,会查找以 exec 为前缀的函数来执行,相当于引擎内部的约定
声明 CustomThunk 函数需要使用 DECLARE_FUNCTION,定义函数使用宏 DEFINE_FUNCTION
// .h 文件
UFUNCTION(BlueprintCallable, CustomThunk, Category = "Utilities|Instanced Struct", meta = (CustomStructureParam = "Value", BlueprintInternalUseOnly="true", NativeMakeFunc))
static FInstancedStruct MakeInstancedStruct(const int32& Value);
DECLARE_FUNCTION(execMakeInstancedStruct);
// .cpp 文件
FInstancedStruct UStructUtilsFunctionLibrary::MakeInstancedStruct(const int32& Value)
{
// We should never hit this! stubs to avoid NoExport on the class.
checkNoEntry();
return {};
}
DEFINE_FUNCTION(UStructUtilsFunctionLibrary::execMakeInstancedStruct)
{
//
}
CustomStructureParam允许名为Value的参数接受多种不同类型的数据AutoCreateRefTerm会自动为标记为通配符的结构体参数创建引用术语
通过蓝图虚拟机的堆栈可以获得函数参数等信息
DEFINE_FUNCTION(UStructUtilsFunctionLibrary::execMakeInstancedStruct)
{
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FStructProperty>(nullptr); // 从蓝图虚拟机的堆栈中获取下一个 FStructProperty 类型的属性,这通常是函数的参数
const FStructProperty* ValueProp = CastField<FStructProperty>(Stack.MostRecentProperty); // 尝试将获取到的属性转换为 FStructProperty 类型,这代表结构体
const void* ValuePtr = Stack.MostRecentPropertyAddress; // 获取目标结构体的地址
// ...
}
如果函数存在多个参数,那就需要多次调用 Stack.StepCompiledIn 直到参数全部获取,当然也 Unreal 也提供了一些宏来方便的获取参数
以 UAnimationAttributeBlueprintLibrary::execSetAttributeKey 为例
UFUNCTION(BlueprintCallable, CustomThunk, Category = "Animation|Attributes", meta=(CustomStructureParam="Value", BlueprintInternalUseOnly="true"))
static bool SetAttributeKey(TScriptInterface<IAnimationDataController> AnimationDataController, const FAnimationAttributeIdentifier& AttributeIdentifier, float Time, const int32& Value);
DEFINE_FUNCTION(UAnimationAttributeBlueprintLibrary::execSetAttributeKey)
{
P_GET_TINTERFACE(IAnimationDataController, AnimationDataController);
P_GET_STRUCT_REF(FAnimationAttributeIdentifier, AttributeIdentifier);
P_GET_PROPERTY(FFloatProperty, TimeInterval);
Stack.MostRecentProperty = nullptr;
Stack.MostRecentPropertyAddress = nullptr;
Stack.StepCompiledIn<FStructProperty>(nullptr);
const FStructProperty* ItemProperty = CastField<FStructProperty>(Stack.MostRecentProperty);
void* ItemDataPtr = Stack.MostRecentPropertyAddress;
// ...
}
由于函数的前几个参数明确了类型 IAnimationDataController、FAnimationAttributeIdentifier、float,分别使用 P_GET_TINTERFACE、P_GET_STRUCT_REF 和 P_GET_PROPERTY 来获取对应的数据
P_GET_TINTERFACE获取接口(Interface)类型属性P_GET_STRUCT_REF获取结构体(STRUCT)类型属性
最后一个传递的是 CustomStructureParam 标记的参数,可以接受任意类型,这里期望的是一个结构体,所以使用 FStructProperty 来接受,由于提前不知道结构体的类型,所以不能使用 P_GET_STRUCT_REF
根据 FStructProperty 可以得到结构体的属性列表和值,进而可以根据需要进行后续操作
DEFINE_FUNCTION(UJsonBlueprintFunctionLibrary::execGetField)
{
P_GET_STRUCT_REF(FJsonObjectWrapper, JsonObject);
P_GET_PROPERTY(FStrProperty, FieldName);
Stack.StepCompiledIn<FProperty>(nullptr);
FProperty* ValueProp = Stack.MostRecentProperty;
void* ValuePtr = Stack.MostRecentPropertyAddress;
P_FINISH;
if (!ValueProp || !ValuePtr)
{
// 错误警告
}
bool bResult;
if (FieldName.IsEmpty())
{
// 特殊处理
}
P_NATIVE_BEGIN
bResult = JsonFieldToProperty(FieldName, JsonObject, ValueProp, ValuePtr);
P_NATIVE_END
*static_cast<bool*>(RESULT_PARAM) = bResult;
}
继续以 execMakeInstancedStruct 为例,需要注意的是 P_FINISH、P_NATIVE_BEGIN、P_NATIVE_END 和 RESULT_PARAM
P_FINISH: 宏之前为获取函数参数的代码块P_NATIVE_BEGIN 和 P_NATIVE_END : 宏包裹着真正执行该函数功能的被调用函数,若是泛型蓝图节点,此处调用该函数的泛型版本(泛型函数)RESULT_PARAM: 表示函数返回值