C++ Introspection and ContainerPtrToValuePtr

I’m doing a full introspection of an actor class, diging in arrays and structs to access basic properties. I’m facing issue with a crash in a FProperty::ContainerPtrToValuePtr. See the code below :

Code

void UnrCompilationManager::BrowseActor(const AActor* aActor)
{
    for (TFieldIterator<FProperty> vPropIt(aActor->GetClass(), EFieldIterationFlags::IncludeSuper); vPropIt; ++vPropIt)
    {
       BrowseAnyProperty(aActor, *vPropIt, aActor->GetName());
    }
}

void UnrCompilationManager::BrowseAnyProperty(const AActor* aActor, const FProperty* aProperty, const FString& aRelativePath)
{
    if (const FArrayProperty* vArrayProperty = CastField<const FArrayProperty>(aProperty))
    {
       auto vArrayName = vArrayProperty->GetAuthoredName();

       auto test = vArrayProperty->ContainerPtrToValuePtr<void>(aActor); // CRASH HERE WITH MY EXAMPLE
       
       FScriptArrayHelper vArrayHelper(vArrayProperty, vArrayProperty->ContainerPtrToValuePtr<void>(aActor));
       auto vCount = vArrayHelper.Num();
       
       for (int vIndex = 0; vIndex < vCount; vIndex++)
       {
          auto vItemPath = aRelativePath + "." + vArrayName + "[" + FString::FromInt(vIndex) + "]";
          BrowseAnyProperty(aActor, vArrayProperty->Inner, vItemPath);
       }
    }
    else if (const FStructProperty* vStructProperty = CastField<const FStructProperty>(aProperty))
    {
       BrowseStructProperty(aActor, vStructProperty, aRelativePath);
    }
    else
    {
       HandleBasicProperty(aActor, aProperty, aRelativePath);
    }
}

void UnrCompilationManager::BrowseStructProperty(const AActor* aActor, const FStructProperty* aStructProperty, const FString& aRelativePath)
{
    auto vStructPropertyName = aStructProperty->GetAuthoredName();
    auto vStructName = aStructProperty->Struct->GetName();
    auto vStructPath = aRelativePath + "." + vStructPropertyName;
    
    UE_LOG(LogTemp, Display, TEXT("%s (%s)"), *vStructPath, *vStructName);

    for (TFieldIterator<FProperty> vPropIt(aStructProperty->Struct, EFieldIterationFlags::IncludeSuper); vPropIt; ++vPropIt)
    {
       BrowseAnyProperty(aActor, *vPropIt, vStructPath);
    }
}

void UnrCompilationManager::HandleBasicProperty(const AActor* aActor, const FProperty* aProperty, const FString& aRelativePath)
{
    auto vPropertyPath = aRelativePath + "." + aProperty->GetAuthoredName();
    FString vIsNative = aProperty->IsNative() ? TEXT("CPP") : TEXT("BP");
    
    UE_LOG(LogTemp, Display, TEXT("%s %s"), *vPropertyPath, *vIsNative);
}

In the level

  • a BP struct FUnrSigSupport with an FString variable named « Name »
  • a BP struct FUnrSigSignal with a TArray variable of FUnrSigSupport named « Supports »
  • a BP actor BP_MySignal parented from AActor with a FUnrSigSupport variable named « Model » that contains some items added in the BP editor.

Problem

On the example, I have a crash on the call of ContainerPtrToValuePtr. I have the intuition that when I’m diging deeper in TArray or structs I need to pass something else to ContainerPtrToValuePtr than the root actor « aActor » (something relative to the array) but I probably missed something and didn’t find any accurate documentation to fully understand the behaviour of ContainerPtrToValuePtr. Any idea or additional informations that could help?

image

The container for the value within the array is no longer the actor, it’s the address of the value in array, which you can get via vArrayHelper.GetRawPtr(vIndex). The type would be void* so you should change the signature of your function. A container can be an actor, an object, a struct, or basically nothing in case of inner properties within container types (in which case the offset will be zero so ContainerPtrToValuePtr is basically returning the pointer you give it, but it’s good practice to keep uniform code regardless).

Similarly in BrowseStructProperty, the container for struct properties is the address of the struct and not the actor.

void BrowseStructProperty(void* Container, FStructProperty* StructProp)
{
    void* StructValue = StructProp->ContainerPtrToValuePtr<void>(Container);
    for (TFieldIterator PropIt(StructProp->Struct)...)
        BrowseAnyProperty(StructValue, *PropIt, Path);
}
1 Like

Thanks A LOT for the answer.

Since then I’ve made some progress to pass the container pointer for each recursive call with a new parameter aContainerPtr. But still without full success. The crash now happen when resolving the terminal property of this hierarchy : AActor > Struct > TArray > Struct > Property. Check “CRASH HERE” comment in source below.

  1. Regarding the array, I was using a auto vArrayItemPtr = vArrayProperty->ContainerPtrToValuePtr<void>(vArrayPtr, vIndex), not sure is not the same as your advised vArrayHelper.GetRawPtr(vIndex)

  2. Regarding the struct, I’ve trying to propagate correctly the container pointer without success.

  3. Could you check the updated code below, I think we are close to the solution! My repro case is quite simple. I’ve created:

    • a VP struct with some interger and FString variable.
    • a BP actor with:
      • a TArray of this struct and 2 items
      • a basic variable of this struct
    • a level with one instance of the actor

Even without the array variable, it crash on the basic struct!

void UnrCompilationManager::BrowseActor(const AActor* aActor)
{
	for (TFieldIterator<FProperty> vPropIt(aActor->GetClass(), EFieldIterationFlags::IncludeSuper); vPropIt; ++vPropIt)
	{
		auto vProperty = *vPropIt;
		BrowseAnyProperty(*vPropIt, aActor, aActor->GetName());
	}
}

void UnrCompilationManager::BrowseAnyProperty(const FProperty* aProperty, const void* aContainerPtr, const FString& aRelativePath)
{
	if (const FArrayProperty* vArrayProperty = CastField<const FArrayProperty>(aProperty))
	{
		auto vArrayName = vArrayProperty->GetAuthoredName();
		auto vArrayPtr = vArrayProperty->ContainerPtrToValuePtr<void>(aContainerPtr);
		
		FScriptArrayHelper vArrayHelper(vArrayProperty, vArrayPtr);
		auto vCount = vArrayHelper.Num();
		
		for (int vIndex = 0; vIndex < vCount; vIndex++)
		{
			auto vItemPath = aRelativePath + "." + vArrayName + "[" + FString::FromInt(vIndex) + "]";
			// auto vArrayItemPtr = vArrayProperty->ContainerPtrToValuePtr<void>(vArrayPtr, vIndex);
			auto vArrayItemPtr = vArrayHelper.GetRawPtr(vIndex);

			BrowseAnyProperty(vArrayProperty->Inner, vArrayItemPtr, vItemPath);
		}
	}
	else if (const FStructProperty* vStructProperty = CastField<const FStructProperty>(aProperty))
	{
		// ??? auto vStructPtr = vStructProperty->ContainerPtrToValuePtr<void>(aContainerPtr);
		BrowseStructProperty(vStructProperty, aContainerPtr, aRelativePath);
	}
	else
	{
		// ?? auto vPropertyPtr = aProperty->ContainerPtrToValuePtr<void>(aContainerPtr);
		DumpBasicProperty(aProperty, aContainerPtr, aRelativePath);
	}
}

void UnrCompilationManager::BrowseStructProperty(const FStructProperty* aStructProperty, const void* aContainerPtr, const FString& aRelativePath)
{
	auto vStructPropertyName = aStructProperty->GetAuthoredName();
	auto vStructName = aStructProperty->Struct->GetName();
	auto vStructPath = aRelativePath + "." + vStructName;
	
	UE_LOG(LogTemp, Display, TEXT("%s (%s)"), *vStructPath, *vStructName);

	for (TFieldIterator<FProperty> vPropIt(aStructProperty->Struct, EFieldIterationFlags::IncludeSuper); vPropIt; ++vPropIt)
	{
		auto vProperty = *vPropIt;
		auto vPropertyPtr = vProperty->ContainerPtrToValuePtr<void>(aContainerPtr);

		BrowseAnyProperty(*vPropIt, vPropertyPtr, aRelativePath);
	}
}

void UnrCompilationManager::DumpBasicProperty(const FProperty* aProperty, const void* aContainerPtr, const FString& aRelativePath)
{
	auto vPropertyPath = aRelativePath + "." + aProperty->GetAuthoredName();
	FString vIsNative = aProperty->IsNative() ? TEXT("CPP") : TEXT("BP");
	FString vValue = "Unknown";

	if (aProperty->GetClass() == FIntProperty::StaticClass())
	{
		auto vValuePtr = aProperty->ContainerPtrToValuePtr<int>(aContainerPtr);
		vValue = FString::FromInt(*vValuePtr);
	}
	else if (aProperty->GetClass() == FStrProperty::StaticClass())
	{
		auto vValuePtr = aProperty->ContainerPtrToValuePtr<FString>(aContainerPtr); // <== CRASH HERE
		vValue = *vValuePtr;
	}
	
	UE_LOG(LogTemp, Display, TEXT("%s %s %s"), *vPropertyPath, *vIsNative, *vValue);
}

Your struct function is still not right, you are skipping a step.

You first need to get a pointer to the value of the struct (using the FStructProperty), then use it to get a property value inside the struct (using vPropIt) :

void* aStructValuePtr = aStructProperty->ContainerPtrToValuePtr<void>(aContainerPtr);

for (TFieldIterator<FProperty> vPropIt(aStructProperty->Struct, EFieldIterationFlags::IncludeSuper); vPropIt; ++vPropIt)
{
    auto vProperty = *vPropIt;
    auto vPropertyPtr = vProperty->ContainerPtrToValuePtr<void>(aStructValuePtr);

    BrowseAnyProperty(*vPropIt, vPropertyPtr, aRelativePath);
}

Alternatively, you could otherwise do it in the calling function :

else if (auto vStructProperty = CastField<FStructProperty>(aProperty))
{
    auto vStructPtr = vStructProperty->ContainerPtrToValuePtr<void>(aContainerPtr);
    BrowseStructProperty(vStructProperty, vStructPtr, aRelativePath);
}
1 Like

Still get a wrong pointer that crash. Even with a basic actor with a Struct variable. Could you try on your own please?

void UnrCompilationManager::BrowseActor(const AActor* aActor)
{
	for (TFieldIterator<FProperty> vPropIt(aActor->GetClass(), EFieldIterationFlags::IncludeSuper); vPropIt; ++vPropIt)
	{
		auto vProperty = *vPropIt;
		BrowseAnyProperty(vProperty, aActor, aActor->GetName());
	}
}

void UnrCompilationManager::BrowseAnyProperty(const FProperty* aProperty, const void* aContainerPtr, const FString& aRelativePath)
{
	if (const FArrayProperty* vArrayProperty = CastField<const FArrayProperty>(aProperty))
	{
		auto vArrayName = vArrayProperty->GetAuthoredName();
		FScriptArrayHelper vArrayHelper(vArrayProperty, aContainerPtr);
		auto vCount = vArrayHelper.Num();
		
		for (int vIndex = 0; vIndex < vCount; vIndex++)
		{
			auto vItemPath = aRelativePath + "." + vArrayName + "[" + FString::FromInt(vIndex) + "]";
			auto vArrayItemPtr = vArrayHelper.GetRawPtr(vIndex);

			BrowseAnyProperty(vArrayProperty->Inner, vArrayItemPtr, vItemPath);
		}
	}
	else if (const FStructProperty* vStructProperty = CastField<const FStructProperty>(aProperty))
	{
		auto vStructPropertyName = vStructProperty->GetAuthoredName();
		auto vStructName = vStructProperty->Struct->GetName();
		auto vStructPath = aRelativePath + "." + vStructPropertyName;

		UE_LOG(LogTemp, Display, TEXT("%s (%s)"), *vStructPath, *vStructName);

		BrowseStructProperty(vStructProperty, aContainerPtr, vStructPath);
	}
	else
	{
		DumpBasicProperty(aProperty, aContainerPtr, aRelativePath);
	}
}

void UnrCompilationManager::BrowseStructProperty(const FStructProperty* aStructProperty, const void* aContainerPtr, const FString& aRelativePath)
{
	auto vStructPtr = aStructProperty->ContainerPtrToValuePtr<void>(aContainerPtr);

	for (TFieldIterator<FProperty> vPropIt(aStructProperty->Struct, EFieldIterationFlags::IncludeSuper); vPropIt; ++vPropIt)
	{
		auto vProperty = *vPropIt;
		auto vPropertyPtr = vProperty->ContainerPtrToValuePtr<void>(vStructPtr);
		BrowseAnyProperty(vProperty, vPropertyPtr, aRelativePath);
	}
}

void UnrCompilationManager::DumpBasicProperty(const FProperty* aProperty, const void* aContainerPtr, const FString& aRelativePath)
{
	auto vPropertyPath = aRelativePath + "." + aProperty->GetAuthoredName();
	FString vIsNative = aProperty->IsNative() ? TEXT("CPP") : TEXT("BP");
	FString vValue = "Unknown";

	if (aProperty->GetClass() == FIntProperty::StaticClass())
	{
		auto vValuePtr = aProperty->ContainerPtrToValuePtr<int>(aContainerPtr);
		vValue = FString::FromInt(*vValuePtr);
	}
	else if (aProperty->GetClass() == FStrProperty::StaticClass())
	{
		auto vValuePtr = aProperty->ContainerPtrToValuePtr<FString>(aContainerPtr);
		vValue = *vValuePtr; // <== CRASH HERE
	}
	
	UE_LOG(LogTemp, Display, TEXT("%s %s %s"), *vPropertyPath, *vIsNative, *vValue);
}

It actually works for structs, the issue is with arrays - same problem - the constructor of FScriptArrayHelper needs pointer to the array itself and not its container (the actor).

And the reason it’s crashing on an array is because you’re iterating properties with ::IncludeSuper so once it’s done with your class it goes over to the superclass (AActor) which has tons of properties. For testing purposes you could use ExcludeSuper instead.

This should work regardless

auto vArrayPtr = vArrayProperty->ContainerPtrToValuePtr<void>(aContainerPtr);
FScriptArrayHelper vArrayHelper(vArrayProperty, vArrayPtr);

WORKING SOLUTION. Thanks @Chatouille

void AUnrSimulationActor::Dump() const
{
	for (TFieldIterator<FProperty> vPropIt(GetClass(), EFieldIterationFlags::IncludeSuper); vPropIt; ++vPropIt)
	{
		auto vProperty = *vPropIt;
		DumpAnyProperty(vProperty, this, GetName());
	}
}

void AUnrSimulationActor::DumpAnyProperty(const FProperty* aProperty, const void* aContainerPtr, const FString& aRelativePath, bool aAddPropertyNameToPath) const
{
	if (const FArrayProperty* vArrayProperty = CastField<const FArrayProperty>(aProperty))
	{
		auto vArrayName = vArrayProperty->GetAuthoredName();
		auto vArrayPtr = vArrayProperty->ContainerPtrToValuePtr<void>(aContainerPtr);
		FScriptArrayHelper vArrayHelper(vArrayProperty, vArrayPtr);
		auto vCount = vArrayHelper.Num();

		for (int vIndex = 0; vIndex < vCount; vIndex++)
		{
			auto vItemPath = aRelativePath + "." + vArrayName + "[" + FString::FromInt(vIndex) + "]";
			auto vArrayItemPtr = vArrayHelper.GetRawPtr(vIndex);

			DumpAnyProperty(vArrayProperty->Inner, vArrayItemPtr, vItemPath, false);
		}
	}
	else if (const FStructProperty* vStructProperty = CastField<const FStructProperty>(aProperty))
	{
		auto vStructPropertyName = vStructProperty->GetAuthoredName();
		auto vStructName = vStructProperty->Struct->GetName();
		auto vStructPath = aAddPropertyNameToPath ? aRelativePath + "." + vStructPropertyName : aRelativePath;

		UE_LOG(LogTemp, Display, TEXT("%s (%s)"), *vStructPath, *vStructName);

		DumpStructProperty(vStructProperty, aContainerPtr, vStructPath);
	}
	else
	{
		DumpBasicProperty(aProperty, aContainerPtr, aRelativePath);
	}
}

void AUnrSimulationActor::DumpStructProperty(const FStructProperty* aStructProperty, const void* aContainerPtr, const FString& aRelativePath) const
{
	auto vStructPtr = aStructProperty->ContainerPtrToValuePtr<void>(aContainerPtr);

	for (TFieldIterator<FProperty> vPropIt(aStructProperty->Struct, EFieldIterationFlags::IncludeSuper); vPropIt; ++vPropIt)
	{
		auto vProperty = *vPropIt;
		DumpAnyProperty(vProperty, vStructPtr, aRelativePath);
	}
}

void AUnrSimulationActor::DumpBasicProperty(const FProperty* aProperty, const void* aContainerPtr, const FString& aRelativePath) const
{
	auto vPropertyPath = aRelativePath + "." + aProperty->GetAuthoredName();
	FString vIsNative = aProperty->IsNative() ? TEXT("CPP") : TEXT("BP");
	FString vValue = "Unknown";

	if (aProperty->GetClass() == FIntProperty::StaticClass())
	{
		auto vValuePtr = aProperty->ContainerPtrToValuePtr<int>(aContainerPtr);
		vValue = FString::FromInt(*vValuePtr);
	}
	else if (aProperty->GetClass() == FDoubleProperty::StaticClass())
	{
		auto vValuePtr = aProperty->ContainerPtrToValuePtr<double>(aContainerPtr);
		vValue = FString::SanitizeFloat(*vValuePtr);
	}
	else if (aProperty->GetClass() == FStrProperty::StaticClass())
	{
		auto vValuePtr = aProperty->ContainerPtrToValuePtr<FString>(aContainerPtr);
		vValue = *vValuePtr;
	}
	else if (aProperty->GetClass() == FBoolProperty::StaticClass())
	{
		auto vValuePtr = aProperty->ContainerPtrToValuePtr<bool>(aContainerPtr);
		vValue = *vValuePtr ? "true" : "false";
	}

	UE_LOG(LogTemp, Display, TEXT("%s %s %s"), *vPropertyPath, *vIsNative, *vValue);
}

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.