# CustomChunk 在计算机编程中,`Thunk` 是一个特殊的函数,其主要目的是延迟计算或调用,将一个多参数函数转换为一个单参数的延迟执行版本 在 `Unreal` 中 `Thunk` 用于将蓝图中的事件或者函数调用转换为 C++ 中的函数调用。 Thunk 函数充当中间层,确保蓝图和 C++ 之间的通信和数据传递能够正常运行 `CustomThunk` 顾名思义就是自定义 `Thunk` 函数,用于告诉引擎这个函数不是自动生成的,而是开发者提供的 这种自定义的 `Thunk` 函数允许开发者直接与**蓝图虚拟机的堆栈**进行交互,从而可以处理更复杂的逻辑,比如**动态参数类型**的处理、**泛型编程**或者在运行时进行特定的**内存操作** CustomThunk 定义的函数,通常需要通过 `DECLARE_FUNCTION` 宏中以 `exec` 前缀来声明。这个 `exec` 是需要遵守的约定,因为 `Unreal` 的蓝图虚拟机在处理函数调用时,会查找以 `exec` 为前缀的函数来执行,相当于引擎内部的约定 声明 `CustomThunk` 函数需要使用 `DECLARE_FUNCTION`,定义函数使用宏 `DEFINE_FUNCTION` ```cpp // .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` 会自动为标记为通配符的结构体参数创建引用术语 通过蓝图虚拟机的堆栈可以获得函数参数等信息 ```cpp DEFINE_FUNCTION(UStructUtilsFunctionLibrary::execMakeInstancedStruct) { Stack.MostRecentPropertyAddress = nullptr; Stack.MostRecentPropertyContainer = nullptr; Stack.StepCompiledIn(nullptr); // 从蓝图虚拟机的堆栈中获取下一个 FStructProperty 类型的属性,这通常是函数的参数 const FStructProperty* ValueProp = CastField(Stack.MostRecentProperty); // 尝试将获取到的属性转换为 FStructProperty 类型,这代表结构体 const void* ValuePtr = Stack.MostRecentPropertyAddress; // 获取目标结构体的地址 // ... } ``` 如果函数存在多个参数,那就需要多次调用 `Stack.StepCompiledIn` 直到参数全部获取,当然也 Unreal 也提供了一些宏来方便的获取参数 以 `UAnimationAttributeBlueprintLibrary::execSetAttributeKey` 为例 ```cpp UFUNCTION(BlueprintCallable, CustomThunk, Category = "Animation|Attributes", meta=(CustomStructureParam="Value", BlueprintInternalUseOnly="true")) static bool SetAttributeKey(TScriptInterface 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(nullptr); const FStructProperty* ItemProperty = CastField(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` 可以得到结构体的属性列表和值,进而可以根据需要进行后续操作 ```cpp DEFINE_FUNCTION(UJsonBlueprintFunctionLibrary::execGetField) { P_GET_STRUCT_REF(FJsonObjectWrapper, JsonObject); P_GET_PROPERTY(FStrProperty, FieldName); Stack.StepCompiledIn(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(RESULT_PARAM) = bResult; } ``` 继续以 `execMakeInstancedStruct` 为例,需要注意的是 `P_FINISH`、`P_NATIVE_BEGIN`、`P_NATIVE_END` 和 `RESULT_PARAM` - `P_FINISH`: 宏之前为获取函数参数的代码块 - `P_NATIVE_BEGIN` 和 `P_NATIVE_END` : 宏包裹着真正执行该函数功能的被调用函数,若是泛型蓝图节点,此处调用该函数的泛型版本(泛型函数) - `RESULT_PARAM`: 表示函数返回值