UE4 CustomThunk笔记


.h文件

  • 在UFUNCTION()内添加CustomThunk关键字,使UHT在生成.generate.h文件时跳过该函数的execTestCustomThunk函数的生成
UFUNCTION(BlueprintCallable, CustomThunk)
    static void TestCustomThunk();

  • 手动添加execTestCustomThunk函数
DECLARE_FUNCTION(execTestCustomThunk)
{
    ....
}

  • 在execTestCustomThunk内处理TestCustomThunk()传入的参数
    execTestCustomThunk函数传入的参数为UObject* Content, FFrame& Stack, void* Z_Param_Result
    其中Stack为传入参数的栈,Z_Param_Result为返回值的地址

    参数可以分为
    UObject
    enum
    struct(FVector, FRotator等)
    TArray
    TMap
    TSet
    基本数据类型(int32, bool, double, FString等)
    其中每一种分别对应两个宏,一个值传递宏,一个引用传递宏,宏内定义了创建局部变量,从Stack中逐步取地址并取值等操作
    参数在Stack中存放的顺序即为TestCustomThunk()传参时传入的顺序

例如TestCustomThunk()的传参如果为:

UFUNCTION(BlueprintCallable, CustomThunk)
  static void TestCustomThunk(int32 a, const int32& b);

则在DECLARE_FUNCTION内添加:

P_GET_PROPERTY(UIntProperty,Param_a); 
P_GET_PROPERTY_REF(UIntProperty,Param_b);

其中UIntProperty为参数的类型对应的类,具体哪种数据类型对应哪个类参考Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h
Param_a为参数名字(从4.25起,UIntProperty等类型从U开头改成了F开头,例如FIntProperty)
具体宏种类参考/Engine/Source/Runtime/CoreUObject/Public/UObject/ScriptMacros.h
其他种类的写法可自行在头文件中定义一个函数后编译,参考UHT生成的.generate.h内的内容

  • 如果需要让参数变成泛型引脚,则在meta中添加CustomStructureParam标识参数,不同参数之间用","隔开
UFUNCTION(BlueprintCallable, CustomThunk,  meta = (CustomStructureParam = "a,b"))
  static void TestCustomThunk(const int32& a, int32& b);

  • 如果需要让多个泛型引脚保持相同的数据类型,则在meta中添加SetParam标识参数,不同参数之间用"|"隔开
UFUNCTION(BlueprintCallable, CustomThunk,  meta = (SetParam = "a|b"))
  static void TestCustomThunk(const int32& a, int32& b);

其中const int32& a为传入参数,int32& b为返回参数

  • 如果需要让某个跟随着其他引脚一起改变数据类型的引脚在类型改变之后,在节点上可以填入初始值而不需要再接入其他引脚的话,则在meta中添加AutoCreateRefTerm标识参数,不同参数之间用","隔开
UFUNCTION(BlueprintCallable, CustomThunk,  meta = (AutoCreateRefTerm = "a,b"))
  static void TestCustomThunk(const int32& a, const int32& b);

  • 被标记为泛型的参数,需要手动获取参数的类型和地址
Stack.StepCompiledIn(NULL);
UProperty* ValueProperty = Cast(Stack.MostRecentProperty);
void* ValueAddr = Stack.MostRecentPropertyAddress;

执行StepCompiledIn让MostRecentPropertyAddress指向下一个参数的地址

  • 被标记有初始值的引脚,因为参数不是从节点外传入的,需要我们自行在execXXXX函数内为其分配内存
Stack.StepCompiledIn(NULL);
UProperty* ValueProperty = Cast(Stack.MostRecentProperty);
const int32 PropertySize = ValueProperty->ElementSize * ValueProperty->ArrayDim;
void* StorageSpace = FMemory::Malloc(PropertySize); //需要释放
ValueProperty->InitializeValue(StorageSpace);
StorageSpace = Stack.MostRecentPropertyAddress;

最后把参数传入真正执行的函数中,函数名字没有要求,在UHT生成的.generated.h中,这个函数通常就是原函数的定义
使用FMemory::Malloc()分配的内存需要在参数传递之后释放,或者把参数指针传入执行函数,在执行函数中释放

FMemory::Free(StorageSpace);

.cpp文件

  • 在cpp文件中,通常情况下如果是通过宏获取的参数,在传入执行函数时,会保持原本的数据类型,但是如果是通过泛型引脚传入的参数,则需要我们在执行函数中自行判断其数据类型,比如下面这个函数
void TestCustomThunk(void* ValueAddr , UProperty* ValueProperty)
{ 
  ...
}

我们在头文件的execXXXX函数中获取到了它的参数地址以及存有类型的变量地址,则可以通过下面的代码来判断它的数据类型

void TestCustomThunk(void* ValueAddr , UProperty* ValueProperty)
{ 
  if (InValueType->IsA(UFloatProperty::StaticClass()))
  {
  ...
  }
  else if (InValueType->IsA(UIntProperty::StaticClass()))
  {
  ...
  }
  else if (InValueType->IsA(UStructProperty::StaticClass()))
  {
    UStructProperty* StructProp = CastChecked(InValueType);
    if (StructProp->Struct == TBaseStructure::Get())
    {
    ...
    }
    else if (StructProp->Struct == TBaseStructure::Get())
    {
    ...
    }
  }
}

同样的,其他类型可参考Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h

  • 通过参数类型和参数地址获取到参数的指针
UFloatProperty* FloatProperty = Cast(ValueType);
float* Value = FloatProperty->GetPropertyValuePtr(ValueAddr);//基础类型

FVector* Value = (FVector*)ValueAddr;//struct类型
UE4