[Tutorial] How to accept wildcard structs in your UFUNCTIONs

While working on exposing JSON handling to Blueprint, I really wished I could serialize and deserialize BP structs. I looked around and couldn’t find anything about making an UFUNCTION take wildcards without having to resort to creating a fullly custom K2_Node class (for which there’s almost no documentation also). I started digging the source and doing some experiments and I finally found a way, which I’m sharing here with you!

DISCLAIMER: Use this at your own risk! I haven’t tested this extensively since most of the stuff it uses isn’t deeply documented (thanks Epic!). You have been warned.

Note: This only works for receiving structs. It won’t work with integers, floats, booleans, strings and other primitive types.

Here’s the complete example:


// Example Blueprint function that receives any struct as input
UFUNCTION(BlueprintCallable, Category = "Example", CustomThunk, meta = (CustomStructureParam = "AnyStruct"))
static void ReceiveSomeStruct(UProperty* AnyStruct);
 
DECLARE_FUNCTION(execReceiveSomeStruct)
{
    // Steps into the stack, walking to the next property in it
    Stack.Step(Stack.Object, NULL);
 
    // Grab the last property found when we walked the stack
    // This does not contains the property value, only its type information
    UStructProperty* StructProperty = ExactCast<UStructProperty>(Stack.MostRecentProperty);
 
    // Grab the base address where the struct actually stores its data
    // This is where the property value is truly stored
    void* StructPtr = Stack.MostRecentPropertyAddress;
 
    // We need this to wrap up the stack
    P_FINISH;
 
    // Iterate through the struct
    IterateThroughStructProperty(StructProperty, StructPtr);
}
 
/* Example function for parsing a single property
* @param Property    the property reflection data
* @param ValuePtr    the pointer to the property value
*/
void ParseProperty(UProperty* Property, void* ValuePtr)
{        
        
    float FloatValue;
    int32 IntValue;
    bool BoolValue;
    FString StringValue;
    FName NameValue;
    FText TextValue;
 
 
    // Here's how to read integer and float properties
    if (UNumericProperty *NumericProperty = Cast<UNumericProperty>(Property))
    {
        if (NumericProperty->IsFloatingPoint())
        {
            FloatValue = NumericProperty->GetFloatingPointPropertyValue(ValuePtr);
        }
        else if (NumericProperty->IsInteger())
        {
            IntValue = NumericProperty->GetSignedIntPropertyValue(ValuePtr);
        }
    }
 
    // How to read booleans
    if (UBoolProperty* BoolProperty = Cast<UBoolProperty>(Property))
    {
        BoolValue = BoolProperty->GetPropertyValue(ValuePtr);
    }
 
    // Reading names
    if (UNameProperty* NameProperty = Cast<UNameProperty>(Property))
    {
        NameValue = NameProperty->GetPropertyValue(ValuePtr);
    }
 
    // Reading strings
    if (UStrProperty* StringProperty = Cast<UStrProperty>(Property))
    {
        StringValue = StringProperty->GetPropertyValue(ValuePtr);
    }
 
    // Reading texts
    if (UTextProperty* TextProperty = Cast<UTextProperty>(Property))
    {
        TextValue = TextProperty->GetPropertyValue(ValuePtr);
    }
 
    // Reading an array
    if (UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Property))
    {
        // We need the helper to get to the items of the array            
        FScriptArrayHelper Helper(ArrayProperty, ValuePtr);
        for (int32 i = 0, n = Helper.Num(); i<n; ++i)
        {    
            ParseProperty(ArrayProperty->Inner, Helper.GetRawPtr(i));
        }
    }
 
    // Reading a nested struct
    if (UStructProperty* StructProperty = Cast<UStructProperty>(Property))
    {
        IterateThroughStructProperty(StructProperty, ValuePtr);
    }
}
 
/*
* Example function for iterating through all properties of a struct
* @param StructProperty    The struct property reflection data
* @param StructPtr        The pointer to the struct value
*/
void IterateThroughStructProperty(UStructProperty* StructProperty, void* StructPtr)
{
    // Walk the structs' properties
    UScriptStruct* Struct = StructProperty->Struct;
    for (TFieldIterator<UProperty> It(Struct); It; ++It)
    {
        UProperty* Property = *It;
 
        // This is the variable name if you need it
        FString VariableName = Property->GetName();
 
        // Never assume ArrayDim is always 1
        for (int32 ArrayIndex = 0; ArrayIndex < Property->ArrayDim; ArrayIndex++)
        {
            // This grabs the pointer to where the property value is stored
            void* ValuePtr = Property->ContainerPtrToValuePtr<void>(StructPtr, ArrayIndex);
 
            // Parse this property
            ParseProperty(Property, ValuePtr);
        }
    }
}


And the blueprint:

TestStruct.JPG&stc=1

&stc=1

Now some explanations (at least from what I could understand):

  • The CustomStructureParam metadata is what tells the Blueprint code generator to treat the pin as a wildcard.
  • The CustomThunk UFUNCTION flag prevents UHT from generating the code that glues the Blueprint to the C++ function and requires you to provider your own. This is necessary because the code generated by UHT would try to extract a UProperty* from the wildcard pin’s value, which would never work. We need to access the actual pin property so we can grab it’s value manually. This is done by using the DECLARE_FUNCTION macro and it gives us low level access to the Stack and allow us to walk through the connected UProperties.
  • This one too me a while to figure out: the UProperty only contains reflection data upon the property (its type). It does not contain the value itself! For that you need the pointer to the “container”, which is where the actual values are stored. Each property only contains the memory offset from the container pointer that leads to the data. This makes sense when you remember that this is used to access C++ data in structs and classes (in which case the “container” is a pointer to the C++ class/struct that contains the value).
  • Fortunately the Stack allows us to get both the UProperty and it’s container address. The Stack.Step() function “steps” through the properties in the stack and stores the information for us.
  • Blueprint structs have strange (and long) property names. Don’t be scared, those are the “true” names of a BP struct’s properties (the names you see in the Editor are the “friendly” names and don’t seem to exist during runtime). I suspect this is done to prevent name collisions or something, but I’m not sure. I’ll have to deal with this in my JSON data.
2 Likes

Daaam, thats some good info.

This is pretty awesome. I am hoping I am can use to create a wild card property and have it determine which method to use based on the struct thats passed in.

That’s awesome!! Thank you, I managed to write a custom data serializer with this :slight_smile:

I basically copy-pasted the code to see what would happen a couple years after this was posted (and because UE still is missing something like this unfortunately).

The “DECLARE_FUNCTION” macro seems to create a static function so the compiler yells at me when it tries to call “IterateThroughStructProperty”. Making the other two functions static kinda defeats the purpose to me (I wanted a virtual “ParseProperty” so each class could try to extract whatever they needed without caring for the struct) and just doesn’t seem the way this was supposed to work (or is it?).

I tried using “DEFINE_FUNCTION” which on the surface seemed to do the same thing, but without making the function static. Alas, other compiler errors hit me in the face.

Any suggestions?

@Allar I am wondering, why something, that is so essential, does not work from house? Do you have contact to developers and by any chance can suggest to them to implement that as In-House possibility?

Is there updated documentation for 4.25 ? UStructProperty has been changed in the most recent API

This should help you. I couldn’t find it either at the start, but it states pretty exactly what you have to change (in general) to get your UStructProperty to compile again.

What's New | Unreal Engine 5.3 Documentation

Yes I have seen the patch notes what I am referring to in my question is that the formerly documented API’s (ex. https://docs.unrealengine.com/en-US/…ies/index.html) Have been removed and replaced with *undocumented *API’s.

Ok Here’s where I’m at with the tutorial.



                               a
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "UObject/UnrealType.h"

#include "Struct.generated.h"

UCLASS()
class UstructBPLibrary : public UBlueprintFunctionLibrary
{
   GENERATED_BODY()

   // Example Blueprint function that receives any struct as input
   UFUNCTION(BlueprintCallable, Category = "Example", CustomThunk, meta = (CustomStructureParam = "AnyStruct"))
   static void ReceiveSomeStruct(UStruct* AnyStruct);

   DECLARE_FUNCTION(execReceiveSomeStruct)
   {
      // Steps into the stack, walking to the next property in it
      Stack.Step(Stack.Object, NULL);

      // Grab the last property found when we walked the stack
      // This does not contains the property value, only its type information
      FProperty* StructProperty = CastField<FProperty>(Stack.MostRecentProperty);

      // Grab the base address where the struct actually stores its data
      // This is where the property value is truly stored
      void* StructPtr = Stack.MostRecentPropertyAddress;

      // We need this to wrap up the stack
      P_FINISH;

      // Iterate through the struct
      IterateThroughStructProperty(StructProperty, StructPtr);
   }

   private:
   /*
   * Example function for iterating through all properties of a struct
   * @param StructProperty The struct property reflection data
   * @param StructPtr The pointer to the struct value
   */
   static void IterateThroughStructProperty(FProperty* StructProperty, void* StructPtr)
   {
      // empty body
   };
};


and here are the compile errors


Severity Code Description File Line
[FONT=Courier New]Error (active) E0757 variable "FProperty" is not a type name ExamplePlugin                 [folder]Public\Struct.h 42
Error (active) E0077 this declaration has no storage class or type specifier ExamplePlugin [folder]Public\Struct.h 10
Error (active) E0757 function "UStruct" is not a type name ExamplePlugin                   [folder]Public\Struct.h 14
Error (active) E0757 overloaded function "UObject" is not a type name ExamplePlugin        [folder]Public\Struct.h 16
Error (active) E0020 identifier "StructProperty" is undefined ExamplePlugin                [folder]Public\Struct.h 23
Error (active) E0109 expression preceding parentheses of apparent call must have (
                     pointer-to-) function type ExamplePlugin                              [folder]Public\Struct.h 23
Error (active) E0757 variable "FProperty" is not a type name ExamplePlugin                 [folder]Public\Struct.h 23


Ok, started from scratch the following compiles and runs without error (on my machine)



#pragma once
// Example Blueprint function that receives any struct as input
#include "Kismet/BlueprintFunctionLibrary.h"
#include "StructBPLibrary.generated.h"

/* 
*	Function library class.
*	Each function in it is expected to be static and represents blueprint node that can be called in any blueprint.
*
*	When declaring function you can define metadata for the node. Key function specifiers will be BlueprintPure and BlueprintCallable.
*	BlueprintPure - means the function does not affect the owning object in any way and thus creates a node without Exec pins.
*	BlueprintCallable - makes a function which can be executed in Blueprints - Thus it has Exec pins.
*	DisplayName - full name of the node, shown when you mouse over the node and in the blueprint drop down menu.
*				Its lets you name the node using characters not allowed in C++ function names.
*	CompactNodeTitle - the word(s) that appear on the node.
*	Keywords -	the list of keywords that helps you to find node when you search for it using Blueprint drop-down menu. 
*				Good example is "Print String" node which you can find also by using keyword "log".
*	Category -	the category your node will be under in the Blueprint drop-down menu.
*
*	For more info on custom blueprint nodes visit documentation:
*	https://wiki.unrealengine.com/Custom_Blueprint_Node_Creation
*/
UCLASS()
class UStructBPLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_UCLASS_BODY()

	UFUNCTION(
		BlueprintCallable, 
		Category = "Example",
		CustomThunk,
		meta = (
			Keywords = "Struct sample test testing",
			CustomStructureParam = "AnyStruct"
	))
	static void ReceiveSomeStruct(UStruct* AnyStruct);

	DECLARE_FUNCTION(execReceiveSomeStruct)
	{
		// Steps into the stack, walking to the next property in it
		Stack.Step(Stack.Object, NULL);

		// Grab the last property found when we walked the stack
		// This does not contains the property value, only its type information
		FProperty* StructProperty = CastField<FProperty>(Stack.MostRecentProperty);

		// Grab the base address where the struct actually stores its data
		// This is where the property value is truly stored
		void* StructPtr = Stack.MostRecentPropertyAddress;

		// We need this to wrap up the stack
		P_FINISH;

		// Iterate through the struct
		ReceiveSomeStruct_impl(StructProperty, StructPtr);
	}

	/*
	* Example function for iterating through all properties of a struct
	* @param StructProperty    The struct property reflection data
	* @param StructPtr        The pointer to the struct value
	*/
	static void ReceiveSomeStruct_impl(FProperty* Property, void* StructPtr)
	{
		UE_LOG(LogTemp, Warning, TEXT("%s"), *Property->GetAuthoredName());
		FStructProperty* StructProperty = CastField<FStructProperty>(Property);
		//check for null
		if (StructProperty)
		{
			// Walk the structs' properties
			for (TFieldIterator<FProperty> PropertyIt(StructProperty->Struct); PropertyIt; ++PropertyIt)
			{
				// This is the variable name if you need it
				UE_LOG(LogTemp, Warning, TEXT("%s"), *PropertyIt->GetAuthoredName());
				// Never assume ArrayDim is always 1
				for (int32 ArrayIndex = 0; ArrayIndex < PropertyIt->ArrayDim; ArrayIndex++)
				{
					// This grabs the pointer to where the property value is stored
					void* ValuePtr = PropertyIt->ContainerPtrToValuePtr<void>(StructPtr, ArrayIndex);

					// Parse this property
					ParseProperty(*PropertyIt, ValuePtr);
				}
			}
		}
	}

	/* Example function for parsing a single property
	* @param Property    the property reflection data
	* @param ValuePtr    the pointer to the property value
	*/
	static void ParseProperty(FProperty* Property, void* ValuePtr)
	{
		float FloatValue;
		int32 IntValue;
		bool BoolValue;
		FString StringValue;
		FName NameValue;
		FText TextValue;

		// Here's how to read integer and float properties
		if (FNumericProperty* NumericProperty = CastField<FNumericProperty>(Property))
		{
			if (NumericProperty->IsFloatingPoint())
			{
				FloatValue = NumericProperty->GetFloatingPointPropertyValue(ValuePtr);
				UE_LOG(LogTemp, Warning, TEXT("integer%d]"), FloatValue);
			}
			else if (NumericProperty->IsInteger())
			{
				IntValue = NumericProperty->GetSignedIntPropertyValue(ValuePtr);
				UE_LOG(LogTemp, Warning, TEXT("integer:'%i'"), IntValue);
			}
		}
		// How to read booleans
		else if (FBoolProperty* BoolProperty = CastField<FBoolProperty>(Property))
		{
			BoolValue = BoolProperty->GetPropertyValue(ValuePtr);
			if (BoolValue) 
			{
				UE_LOG(LogTemp, Warning, TEXT("Bool: True"));
			}
			else 
			{
				UE_LOG(LogTemp, Warning, TEXT("Bool: False"));
			}
		}

		// Reading names
		else if (FNameProperty* NameProperty = CastField<FNameProperty>(Property))
		{
			NameValue = NameProperty->GetPropertyValue(ValuePtr);
			UE_LOG(LogTemp, Warning, TEXT("Name:'%s'"), *NameValue.ToString());

		}

		// Reading strings
		else if (FStrProperty* StringProperty = CastField<FStrProperty>(Property))
		{
			StringValue = StringProperty->GetPropertyValue(ValuePtr);
			UE_LOG(LogTemp, Warning, TEXT("String:'%s'"), *StringValue);
		}

		// Reading texts
		else if (FTextProperty* TextProperty = CastField<FTextProperty>(Property))
		{
			TextValue = TextProperty->GetPropertyValue(ValuePtr);
			UE_LOG(LogTemp, Warning, TEXT("Text:'%s'"), *TextValue.ToString());
		}

		// Reading an array
		else if (FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property))
		{
			// We need the helper to get to the items of the array       
			FScriptArrayHelper Helper(ArrayProperty, ValuePtr);
			for (int32 i = 0, n = Helper.Num(); i < n; ++i)
			{
				UE_LOG(LogTemp, Warning, TEXT("Array:%i"), i);
				ParseProperty(ArrayProperty->Inner, Helper.GetRawPtr(i));
			}
		}

		// Reading a nested struct
		else if (Property)
		{
			ReceiveSomeStruct_impl(Property, ValuePtr);
		}
	}
};


1 Like

This is really helpful! How can I add additional Parameters such as an actor and access it through the DECLARE_FUNCTION or is there any chance to do this in a non static way?

Trying to do this with an array of structs. Any advice?

I’ve found this thread really helpful, so many thanks!

I would like to ask if it’s possible to… somehow autogenerate the struct itself, or at least “fake” it. I’m experimenting with the possibility of having a blueprint version of the function “CallFunctionByNameAndArguments()”, although it’s actually using ProcessEvent() directly.

I’ve gotten as far as having an actual CustomThunk function that includes a wildcard pin, which is then passed to the ProcessEvent() function. Basically I can already get it to execute functions with a single parameter of any type, which is awesome, but of course I want to expand on that.

The ProcessEvent() function can be given a struct for its “Parms” parameter, which should contain members matching the target function’s inputs and outputs. But from what I understand after reading through the code, it doesn’t really need the struct itself, it simply reads the memory addresses of its members to populate the target function’s parameters.

I haven’t gotten yet into how exactly should it be done, but thinking abstractly I was wondering if it wouldn’t be possible to directly generate a UStructProperty object and get its members to point to the original function’s wildcard pins instead inside the CustomThunk exec code.

If not, maybe copy ProcessEvent() function itself but accept an array of parameters instead of a struct? What do you think?

thx man, you made my day

Thank you, this helped me a lot!

If anyone is interested, based on the example above I used the JsonObjectConverter to serialize any blueprint struct:

TSharedPtr<FJsonObject> AMyActor::BlueprintStructToJsonAttributes(FProperty* Property, const void* Value)
{
	if (FStructProperty* StructProperty = CastField<FStructProperty>(Property))
	{
		TSharedPtr<FJsonObject> Out = MakeShared<FJsonObject>();
		if (FJsonObjectConverter::UStructToJsonAttributes(StructProperty->Struct, Value, Out->Values))
		{
			return Out;
		}
	}
	return nullptr;
}

The problem with this was that the names of the properties are somewhat cryptic.
In the end I wrote some custom JsonObjectConverter functions to use the AuthoredName instead.

1 Like