Using Reflection in Unreal Engine

Updated for Unreal Engine 5.0

Reflection is the ability to inspect C++ classes and objects at runtime and gather information about their data types and properties. Normally C++ does not maintain programmer-accessible information about, say, what members a struct or class has. Unreal Engine uses macros such as UCLASS and UPROPERTY to create information about classes, structs, methods, properties, and to make that information available to C++ at runtime.

This enables a program to introspect an object and find out, for example, what properties that object has, what the types of those properties are, and given an instance of that object, what the values of those properties are.

Background

The notes here were made in the course of creating a plugin which checks a project configuration against a known-good collection of configuration settings, for example to check that all the settings required for hardware raytracing have their correct values, and have to options to display and change the current settings, as shown here:

This plugin needs to:

  • identify Unreal Engine classes which are used for project settings
  • identify properties on those classes
  • retrieve the current values of those properties
  • change the current values of those properties

The classes which Unreal Engine uses for project settings cannot be hard-coded because they depend on which plugins the user has loaded; so reflection is a good way of retrieving the required information.

Creating Reflection Data

Unreal Engine uses macros which are embedded in the c++ class declarations to create reflection data.

A class defined using the UCLASS macro like the one below will have reflection data:

UCLASS(config=Engine, defaultconfig)
class UWindowsTargetSettings 
	: public UObject
{
    ...
}

The UCLASS macro has many parameters which control how Unreal Engine treats the class, for example whether it can be accessed using Blueprints. The parameters are defined in https://docs.unrealengine.com. At runtime these parameters can be retrieved from using a c++ API.

Other c++ objects such as enum declarations, functions and properties have corresponding macros (namely UENUM, UFUNCTION and UPROPERTY) which are used in a similar way as the UCLASS macro.

Accessing Reflection Data

Class Information

The entry point for reflection data is using iterators. To iterate over the reflection data for all classes which are derived from UObject you use this:

for (FThreadSafeObjectIterator Itr(UObject::StaticClass()); Itr; ++Itr)
{
	UClass* Class = Itr->GetClass();
    ...
}

To limit the search to classes which are derived from a base class such as UDeveloperSettings you do this:

for (FThreadSafeObjectIterator Itr(UDeveloperSettings::StaticClass()); Itr; ++Itr)
{
	UClass* Class = Itr->GetClass();
    ...
}

UClass

Having obtained a UClass* using an iterator as described above, you can do various useful things including:

Action Function
check if the class is a blueprint or a c++ class Class->IsNative()
retrieve the class name Class->GetName(ClassName)
retrieve the config file where class settings are saved Class->ClassConfigName
retrieve metadata such as the label on the UI settings screen Class->GetMetaDataText("DisplayName")

Properties

From a UClass* you can retrieve information about the properties, also using a iterator like so:

for (TFieldIterator<FProperty> PropIt(Class); PropIt; ++PropIt)
{
	FProperty* Property = *PropIt;
    ...
}

Similar to dealing with a property, you can call various functions on a property: |Action|Function| |:---|:---| |retrieve the property name|Property->GetName()| |retrieve metadata such as the label on the UI settings screen|Property->GetMetaDataText("DisplayName")|

You can retrieve elements of the UPROPERTY declaration. For example given this property declaration:

UPROPERTY(config, EditAnywhere, Category = Shadows, meta = (
    ConsoleVariable = "r.Shadow.Virtual.Enable", 
    DisplayName = "Shadow Map Method",
    ToolTip = "Select the primary shadow mapping method."))
    TEnumAsByte<EShadowMapMethod::Type> ShadowMapMethod;

you can test if the property is a config property declared with UPROPERTY(config) like this:

EPropertyFlags Flags = Property->PropertyFlags;
if ((Flags & EPropertyFlags::CPF_Config) != 0)

and retrieve variables from the metadata like this:

const FText DisplayName = Property->GetMetaDataText("DisplayName");
const FText ConsoleVariable = Property->GetMetaDataText("ConsoleVariable");

The type of a property can be retrieved by either:

  • calling Property->GetCPPType() to return the type as a string, or
  • testing it by casting like so:
if (const FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property))
{
    ...
}

Looking at the Unreal Engine source code casting seems to be the preferred way.

Reading Property Values

The code examples above deal with the static reflection data for properties and classes. They do not need a specific instance of a class object to access that data, it is accessed from static instances such as UDeveloperSettings::StaticClass().

To extract a property value from an actual instance of a class requires retrieving the memory address where that property value is stored. Given an instance of a class and a property pointer retrieved as shown above, we can do this to find the memory address where the property is stored:

FProperty* Property = ...
UClass* Class = ...
const uint8* PropertyAddr = Property->ContainerPtrToValuePtr<uint8>(Class);

we can then cast the property pointer to its derived property class and retrieve the property value using the data address:

if (FStrProperty* StringProperty = CastField<FStrProperty>(Property))
{
    Value = StringProperty->GetPropertyValue(PropertyAddr);
}

Some types of property require more work, for instance numeric properties can be different types, as shown here:

if (FNumericProperty* NumericProperty = CastField<FNumericProperty>(Property))
{
    if (NumericProperty->IsFloatingPoint())
    {
        Value = FString::SanitizeFloat(NumericProperty->GetFloatingPointPropertyValue(PropertyAddr));
    }
	else if (NumericProperty->IsInteger())
    {
        Value = FString::FromInt(NumericProperty->GetSignedIntPropertyValue(PropertyAddr));
    }
}

Enum properties have functions for getting a string representation derived from the enum value, like so:

if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property))
{
    UEnum* EnumDef = EnumProperty->GetEnum();
    FNumericProperty* UnderlyingProperty = EnumProperty->GetUnderlyingProperty();
    int32 IntValue = UnderlyingProperty->GetSignedIntPropertyValue(PropertyAddr);
    Value = EnumDef->GetAuthoredNameStringByValue(IntValue);
}

For an enum declared like this:

enum class EDefaultGraphicsRHI : uint8
{
    DefaultGraphicsRHI_Default = 0 UMETA(DisplayName = "Default"),
    DefaultGraphicsRHI_DX11 = 1 UMETA(DisplayName = "DirectX 11"),
	DefaultGraphicsRHI_DX12 = 2 UMETA(DisplayName = "DirectX 12"),
	DefaultGraphicsRHI_Vulkan = 3 UMETA(DisplayName = "Vulkan"),
};

with a property value of EDefaultGraphicsRHI::DefaultGraphicsRHI_DX11 the GetSignedIntPropertyValue() call will return 1 and the GetAuthoredNameStringByValue(1) call will return the string "DefaultGraphicsRHI_DX11".

Writing Property Values

The process of writing property values is much the same as reading them, firstly we need to get the address where the data is stored like so:

FProperty* Property = ...
UClass* Class = ...
const uint8* PropertyAddr = Property->ContainerPtrToValuePtr<uint8>(Class);

Again we cast the property pointer to see what type it is and set the value using the address:

if (FNumericProperty* NumericProperty = CastField<FNumericProperty>(Property))
{
    if (NumericProperty->IsFloatingPoint())
	{
		NumericProperty->SetFloatingPointPropertyValue(PropertyAddr, NewFloatValue);
    }
}

Each type of property has different methods for setting it. For example setting an enum property from a string value involves calling GetIndexByNameString() to get the int representation of that string:

if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property))
{
	UEnum* EnumDef = EnumProperty->GetEnum();
	FNumericProperty* UnderlyingNumericProperty = 
       EnumProperty->GetUnderlyingProperty();

	int64 NewIntValue = EnumDef
       ->GetIndexByNameString(PushedValue, EGetByNameFlags::None);

	if (NewIntValue == -1)
	{
        UnderlyingNumericProperty->SetIntPropertyValue(PropertyAddr, NewIntValue);
    }
}