How to create UFunction signature?

Is it possible to generate a UFunction object with a proper signature in code without actually implementing a UFUNCTION?

There are special cases where it’s very useful to call blueprint functions directly from code.

It’s easy enough to find the function by name, allocate a buffer on the stack, iterate over and initialize the function’s property fields, call ProcessEvent, and then iterate again to get any output values. There are plenty examples of this.

What’s not so easy is confirming the signature of the function you’re calling, which could easily be changed. The only standard way I know of is UFunction::IsSignatureCompatibleWith(). But that requires having two UFunctions to compare with.

Clearly, making a new function in blueprint does generate a new UFuction, but I’m unable to track down where that happens.

Currently I have a dummy UFUNCTION in code with the expected parameters and use that to compare with the one found in blueprint. But deliberately making a dead function like that feels a bit wrong.

Side question: I’m using FMemory_Alloca_Aligned to allocate the parameter buffer on the stack. In all the examples I’ve found, after calling ProcessEvent, they always iterate the fields again and call DestroyValue_InContainer. Since the buffer is on the stack, what does this accomplish?

Steps to Reproduce

“It’s easy enough to find the function by name, allocate a buffer on the stack, iterate over and initialize the function’s property fields, call ProcessEvent, and then iterate again to get any output values. There are plenty examples of this.”

I just want to confirm with you that when you mention iterating over the property fields, you mean the approach of iterating over Func->ChildProperties?

"What’s not so easy is confirming the signature of the function you’re calling, which could easily be changed. "

The reason for my above question is because that’s what I can think of to confirm the function signature: iterating over ChildProperties (ChildProperties->Next, etc) is how I’d use reflection to inspect a function’s signature.

Can you provide a bit more context about what your use case is for manipulating UFunctions? What can cause a signature to change, is this about reusing cooked assets across different build versions, saved replays across different build versions, etc?

“Clearly, making a new function in blueprint does generate a new UFuction, but I’m unable to track down where that happens.”

Have a look at FBlueprintCompilationManagerImpl::FastGenerateSkeletonClass(), especially this section:

`const auto MakeEventFunction = [&CurrentFieldStorageLocation, MakeFunction, Schema]( FName InName, EFunctionFlags ExtraFnFlags, const TArray<UEdGraphPin*>& InputPins, const TArray< TSharedPtr >& UserPins, UFunction* InSourceFN, bool bInCallInEditor, bool bIsDeprecated, const FString& DeprecationMessage, FKismetUserDeclaredFunctionMetadata* UserDefinedMetaData = nullptr)
{
FField** CurrentParamStorageLocation = nullptr;

UFunction* NewFunction = MakeFunction(
InName,
CurrentFieldStorageLocation,
CurrentParamStorageLocation,
ExtraFnFlags|FUNC_BlueprintCallable|FUNC_BlueprintEvent,
TArray<UK2Node_FunctionResult*>(),
InputPins,
false,
true,
InSourceFN
);


}`I’ll ask internally what DestroyValue_InContainer is designed for. I expect it’s for more special cases than plain old data.

Thanks for elaborating on the use case! It’s good to have as context.

“In all the examples I’ve found, after calling ProcessEvent, they always iterate the fields again and call DestroyValue_InContainer. Since the buffer is on the stack, what does this accomplish?”

Property types have specific destructors, such as containers that free allocated memory and structs with custom destructors via CppStructOps. On the other hand, the params buffer is just allocated memory, so while that memory will be automatically freed on scope exit it wouldn’t call the type-specific destructors. That’s why the call to DestroyValue_InContainer is needed. It’s not necessary for plain-old-data types but since we’re dealing with any FProperty we have to consider the non-POD types.

I believe I answered all your questions and you’re currently unblocked, but if I missed anything please let me know.

Thanks. I’m going to answer your questions in reverse order:

I think MakeFunction might be just what I’ve been looking for. I figured that, since you can create arbitrary functions in blueprint and then get them in code as UFunctions, there must be a way to do it, but I failed to find it before.

The use case is some large system has been implemented entirely in blueprint and we won’t/can’t convert it to code. But we develop a need to at least interact with this blueprint system from code, such as calling its blueprint functions and getting return values. The reason the signatures might change is that someone might literally open the blueprint and change the parameters on the blueprint function. (Yes, there are other approaches to this problem, like making some sidecar component that does nothing more than forward function calls back and forth between code and blueprint. But I’d like to explore other avenues.)

Yes, iterating over the UFunction params and making sure all the expected prams are there and in the right order would work. It’s just a cumbersome bit of code to write and maintain. I liken it to parsing text without regular expressions.

Here’s how I’m calling functions. A blueprint actor has a blueprint function named MyBlueprintFunction that takes a UBarConfigDataAsset and returns an int32. I make a UFUNCTION in code with the same signature just for reference. Then I call MyCppFunction which takes Actor and Config, attempts to call the blueprint version of the function on the actor, and then returns ResultId.

`// .h //

UCLASS()
class UFooComponent: public UActorComponent
{
[…]

int32 MyCppFunction(AActor* Actor, UBarConfigDataAsset* Config);

UPROPERTY(EditAnywhere)
FName MyFunctionName = TEXT(“MyBlueprintFunction”);

private:
// MyFunctionSignature exists just to reference the signature but is never directly called.
UFUNCTION()
int32 MyFunctionSignature(UBarConfigDataAsset* Config) { return INDEX_NONE; }

// .cpp //

int32 UFooComponent::MyCppFunction(AActor* Actor, UBarConfigDataAsset* Config)
{
int32 ResultId = INDEX_NONE;

UFunction* Signature = FindFunction(GET_FUNCTION_NAME_CHECKED(UFooComponent, MyFunctionSignature));
UFunction* Func = Actor->FindFunction(MyFunctionName);

if (Signature && Signature->IsSignatureCompatibleWith(Func, UFunction::GetDefaultIgnoredSignatureCompatibilityFlags() | CPF_ReturnParm))
{
uint8* ParamBuffer = (uint8*)FMemory_Alloca_Aligned(Func->ParmsSize, Func->GetMinAlignment());
FMemory::Memzero(ParamBuffer, Func->ParmsSize);

for (TFieldIterator ParamItr(Func); ParamItr && (ParamItr->PropertyFlags & CPF_Parm); ++ParamItr)
{
ParamItr->InitializeValue_InContainer(ParamBuffer);

if (Config)
{
if (FObjectProperty* ObjectProp = CastField(*ParamItr))
{
if (ObjectProp->PropertyClass == UBarConfigDataAsset::StaticClass())
{
ObjectProp->SetObjectPropertyValue_InContainer(ParamBuffer, Config);
}
}
}
}

Actor->ProcessEvent(Func, ParamBuffer);

for (TFieldIterator ParamItr(Func); ParamItr && (ParamItr->PropertyFlags & CPF_Parm); ++ParamItr)
{
if (FIntProperty* IntProp = ParamItr->PropertyFlags & CPF_OutParm ? CastField(*ParamItr) : nullptr)
{
IntProp->GetValue_InContainer(ParamBuffer, &ResultId);
}

ParamItr->DestroyValue_InContainer(ParamBuffer);
}
}
else
{
ensureMsgf(false, TEXT(“Function signature changed!”));
}

return ResultId;
}`