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 :
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?
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.
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.
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)
Regarding the struct, I’ve trying to propagate correctly the container pointer without success.
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);
}
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);