利用反射读写UProperty记录

一、前言

之前有个需求是要做Proto与UObject的一个对等的相互转化。其中UObject侧的核心就是利用反射机制对UObject和UProperty的获取和修改,虽然不是什么难点,不过部分较为复杂的UProperty(例如TArray和TMap的获取与修改)还是让我踩了一些坑的,在此记录一下。

二、所有的UProperty

preview

三、部分UProperty值的读写

3.1 FIntProperty

读取:

FIntProperty* Int32Property = Cast<FIntProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
int32 Int32Value = Int32Property->GetPropertyValue_InContainer(TargetObject, 0); // TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

const FIntProperty* Int32Property = Cast<FIntProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
Int32Property->SetPropertyValue_InContainer(ResultObject, ResultInt32, 0); // ResultObject为该UProperty所在的UObject实例,数据的接收者。ResultInt32为待写入的值。

3.2 FInt64Property

读取:

FInt64Property* Int64Property = Cast<FInt64Property>(TargetProperty); // TargetProperty为UProperty*格式的对象
int64 Int64Value = Int64Property->GetPropertyValue_InContainer(TargetObject, 0);// TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

FInt64Property* Int64Property = Cast<FInt64Property>(TargetProperty); // TargetProperty为UProperty*格式的对象
Int64Property->SetPropertyValue_InContainer(ResultObject, ResultInt64, 0); // ResultObject为该UProperty所在的UObject实例,数据的接收者。ResultInt64为待写入的值。

3.3 FUInt32Property

读取:

FUInt32Property* Uint32Property = Cast<FUInt32Property>(TargetProperty); // TargetProperty为UProperty*格式的对象
uint32 Uint32Value = Uint32Property->GetPropertyValue_InContainer(TargetObject, 0);// TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

FUInt32Property* Uint32Property = Cast<FUInt32Property>(TargetProperty); // TargetProperty为UProperty*格式的对象
Uint32Property->SetPropertyValue_InContainer(ResultObject, ResultUint32, 0); // ResultObject为该UProperty所在的UObject实例,数据的接收者。ResultUint32为待写入的值。

3.4 FUInt64Property

读取:

FUInt64Property* Uint64Property = Cast<FUInt64Property>(TargetProperty); // TargetProperty为UProperty*格式的对象
uint64 Uint64Value = Uint64Property->GetPropertyValue_InContainer(TargetObject, 0);// TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

FUInt64Property* Uint64Property = Cast<FUInt64Property>(TargetProperty); // TargetProperty为UProperty*格式的对象
Uint64Property->SetPropertyValue_InContainer(ResultObject, ResultUint64, 0); // ResultObject为该UProperty所在的UObject实例,数据的接收者。ResultUint64为待写入的值。

3.5 FDoubleProperty

读取:

FDoubleProperty* DoubleProperty = Cast<FDoubleProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
double DoubleValue = DoubleProperty->GetPropertyValue_InContainer(TargetObject, 0);// TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

FDoubleProperty* DoubleProperty = Cast<FDoubleProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
DoubleProperty->SetPropertyValue_InContainer(ResultObject, ResultDouble, 0); // ResultObject为该UProperty所在的UObject实例,数据的接收者。ResultDouble为待写入的值。

3.6 FFloatProperty

读取:

FFloatProperty* FloatProperty = Cast<FFloatProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
float FloatValue = FloatProperty->GetPropertyValue_InContainer(TargetObject, 0); // TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

FFloatProperty* FloatProperty = Cast<FFloatProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
FloatProperty->SetPropertyValue_InContainer(ResultObject, ResultFloat, 0); // ResultObject为该UProperty所在的UObject实例,数据的接收者。ResultFloat为待写入的值。

3.7 FBoolProperty

读取:

FBoolProperty* BoolProperty = Cast<FBoolProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
bool BoolValue = BoolProperty->GetPropertyValue_InContainer(TargetObject, 0); // TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

FBoolProperty* BoolProperty = Cast<FBoolProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
BoolProperty->SetPropertyValue_InContainer(ResultObject, ResultBool, 0); // ResultObject为该UProperty所在的UObject实例,数据的接收者。ResultBool为待写入的值。

3.8 FByteProperty(TEnumAsByte)

读取:

FByteProperty* ByteProperty = Cast<FByteProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
int32 ResultValue = ByteProperty->GetPropertyValue_InContainer(TargetObject, 0); // TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

FByteProperty* ByteProperty = Cast<FByteProperty>(TargetProperty);// TargetProperty为UProperty*格式的对象
ByteProperty->SetPropertyValue_InContainer(ResultObject, ResultInt32, 0);// ResultObject为该UProperty所在的UObject实例,数据的接收者。ResultInt32为待写入的值。

3.9 FStrProperty

读取:

FStrProperty* StrProperty = Cast<FStrProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
FString StrValue = StrProperty->GetPropertyValue_InContainer(TargetObject, 0); // TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

FStrProperty* StrProperty = Cast<FStrProperty>(TargetProperty);// TargetProperty为UProperty*格式的对象
StrProperty->SetPropertyValue_InContainer(ResultObject, FString(ResultString.c_str()), 0);// ResultObject为该UProperty所在的UObject实例,数据的接收者。FString(ResultString.c_str())为待写入的值。

3.10 FObjectProperty

读取:

FObjectProperty* TargetObjectProperty = CastField<FObjectProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
UObject* ObjectValue = TargetObjectProperty->GetPropertyValue_InContainer(TargetObject, 0); // TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

FObjectProperty* TargetObjectProperty = Cast<FObjectProperty>(TargetProperty);// TargetProperty为UProperty*格式的对象
TargetObjectProperty->SetPropertyValue_InContainer(ResultObject, SubObject, 0);// ResultObject为该UProperty所在的UObject实例,数据的接收者。SubObject为待写入的值。

3.11 FStructProperty

读取:

FStructProperty* TargetStructProperty = CastField<FStructProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
UScriptStruct* SubStruct = TargetStructProperty->Struct; // 获取UScriptStruct
void* SubStructValue = TargetStructProperty->ContainerPtrToValuePtr<void>(TargetObject, 0);// TargetObject为该UProperty所在的UObject实例,数据的来源

for (TFieldIterator<FProperty> It(SubStruct); It; ++It) // 遍历struct下的UProperty
{
    FProperty* SubStructProperty = *It;

    if (FIntProperty* SubStructInt32 = CastField<FIntProperty>(SubStructProperty))
    {
        int32 IntValue = SubStructInt32->GetPropertyValue_InContainer(SubStructValue, 0); // 这里举例子进行读取
        UE_LOG(LogTemp, Warning, TEXT("SubStructInt32: %d"), IntValue);
    }
}

写入:

if (FStructProperty* TargetStructProperty = CastField<FStructProperty>(TargetProperty))
{
    void* SubObject = GetUStructFromMessage(&SubMessage);
    void* SubStructValue = TargetStructProperty->ContainerPtrToValuePtr<void>(ResultObject, 0);// 数据来源
    TargetStructProperty->CopyCompleteValue(SubStructValue, SubObject); // 将数据来源拷贝到数据目的地
}

3.12 FArrayProperty

读取:

// 根据TArray的元素类型不同而有细微不同,这里以int32为例,其他类型替换int32即可
TArray<int32>* TargetInt32s = TargetProperty->ContainerPtrToValuePtr<TArray<int32>>(TargetObject); // TargetProperty为UProperty*格式的对象,TargetObject为该UProperty所在的UObject实例,数据的来源

写入:

TArray的写入操作需要借助FScriptArrayHelper。

  1. 元素类型非UStruct的情况:
FArrayProperty* TargetArrayProperty = Cast<FArrayProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
void* ArrayValuePtr = TargetArrayProperty->ContainerPtrToValuePtr<void>(ResultObject);
FScriptArrayHelper ArrayHelper(TargetArrayProperty, ArrayValuePtr);
switch (FieldCppType) // FieldCppType是TArray元素的类型,根据元素类型不一样,读取方式不一样
{
case CPPTYPE_INT32:
   {
      TArray<int32> TargetArray; // 根据数据源对TargetArray进行数据填充
       
      // 数据填充...
                
      ArrayHelper.MoveAssign(&TargetArray); // 将填充之后的TargetArray赋值给ArrayHelper,即完成了写入
   }
   break;
case CPPTYPE_INT64:
   break;
case CPPTYPE_UINT32:
   break;
case CPPTYPE_UINT64:
   break;
case CPPTYPE_DOUBLE:
   break;
case CPPTYPE_FLOAT:
   break;
case CPPTYPE_BOOL:
   break;
default: ;
}
  1. 元素类型为UStruct的情况:不能使用上面的方法(ArrayHelper.MoveAssign之后的数据是错的)。要借助FScriptArrayHelper中的EmptyAndAddUninitializedValues方法先创建出空的UStruct元素,然后利用FStructProperty中的CopyCompleteValue方法将UStruct的值拷贝进来。
void* ArrayValuePtr = TargetArrayProperty->ContainerPtrToValuePtr<void>(TestMainObject);
FScriptArrayHelper ArrayHelper(TargetArrayProperty, ArrayValuePtr); //创建Array操作的辅助函数
if (FStructProperty* TargetStructProperty = CastField<FStructProperty>(TargetArrayProperty->Inner)) // 判断是否为Struct属性
{
    TArray<FSubStruct*> TargetArray; // 需要写入的数据源
    // 将两个用于测试的UStruct数据写入数据源
    FSubStruct* TestStruct1 = new FSubStruct();
    TestStruct1->SubStructInt32 = 51;
    FSubStruct* TestStruct2 = new FSubStruct();
    TestStruct2->SubStructInt32 = 52;
    TargetArray.Add(TestStruct1);
    TargetArray.Add(TestStruct2);
	// 根据数据源的个数,在数据目的地创建出对应数量的未初始化的UStruct
    ArrayHelper.EmptyAndAddUninitializedValues(TargetArray.Num());
    for (int32 i = 0; i < ArrayHelper.Num(); i++) // 遍历并进行初始化
    {
        void* SingleData = ArrayHelper.GetRawPtr(i);
        SingleData = TargetArray[i];
        // 将数据源的数据拷贝到数据目的地
        TargetStructProperty->CopyCompleteValue(ArrayHelper.GetRawPtr(i), TargetArray[i]);
    }
}

3.13 FMapProperty

读取:

FMapProperty* TargetMapProperty = Cast<FMapProperty>(TargetProperty); // TargetProperty为UProperty*格式的对象
		// 根据key和value类型的不同,进行读取
         if (CastField<FUInt32Property>(TargetMapProperty->KeyProp)) // key为uint32
         {
            if (CastField<FObjectProperty>(TargetMapProperty->ValueProp)) // key为uint32,value为UObject
            {
               TMap<uint32, UObject*>* TargetObjectMap = TargetMapProperty->ContainerPtrToValuePtr<TMap<uint32, UObject*>>(TargetObject); // 读取TMap里面的数据,TargetObject为该UProperty所在的UObject实例,数据的来源
            }
            else if (CastField<FEnumProperty>(TargetMapProperty->ValueProp)) // key为uint32,value为enum
            {
               TMap<uint32, uint8>* TargetObjectMap = TargetMapProperty->ContainerPtrToValuePtr<TMap<uint32, uint8>>(TargetObject);
            }
#pragma endregion
         }
         else if (CastField<FUInt64Property>(TargetMapProperty->KeyProp)) // key为uint64
         {             
         }
         else if (CastField<FIntProperty>(TargetMapProperty->KeyProp)) // key为int32
         {
         }
         else if (CastField<FStrProperty>(TargetMapProperty->KeyProp)) // key为string
         {
         }

写入:

TMap的写入操作需要借助FScriptMapHelper。

  1. 元素类型非UStruct的情况:
    FMapProperty* TargetMapProperty = Cast<FMapProperty>(TargetProperty);
      void* MapValuePtr = TargetMapProperty->ContainerPtrToValuePtr<void*>(ResultObject, 0);
      // 伪动态映射。用于以一种合理的方式处理映射属性
      FScriptMapHelper MapHelper(TargetMapProperty, MapValuePtr);
      
      switch (cpp_type()) // 判断TMap的key是哪种类型
      {
      case CPPTYPE_INT32: // key int32
         {
#pragma region key int32
            switch (cpp_type()) // 判断TMap的value是哪种类型
            {
            case CPPTYPE_INT32: // key int32 value int32
               {
                  TMap<int32, int32> TargetMap; // 对TargetMap进行数据填充
                   
                   // 数据填充...
                   
                  // 将TargetMap写入MapHelper
                  MapHelper.MoveAssign(&TargetMap);
               }
               break;
            case CPPTYPE_INT64: // key int32 value int64
               {
               }
               break;
            case CPPTYPE_UINT32: // key int32 value UINT32
               {
               }
               break;
            case CPPTYPE_UINT64: // key int32 value UINT64
               {
               }
               break;
            case CPPTYPE_DOUBLE: // key int32 value DOUBLE
               {
               }
               break;
            case CPPTYPE_FLOAT: // key int32 value FLOAT
               {
               }
               break;
            case CPPTYPE_BOOL: // key int32 value BOOL
               {
               }
               break;
            case CPPTYPE_ENUM: // key int32 value ENUM
               {
               }
               break;
            case CPPTYPE_STRING: // key int32 value STRING
               {
               }
               break;
            default: ;
            }
#pragma endregion
         }
         break;
      case CPPTYPE_INT64: // key int64
         {
         }
         break;
      case CPPTYPE_UINT32: // key uint32
         {
         }
         break;
      case CPPTYPE_UINT64: // key uint64
         {
         }
         break;
      case CPPTYPE_STRING: // key string
         {
         }
         break;
      default: ;
      }
  1. 元素类型为UStruct的情况:

参考FArrayProperty的写入。利用FScriptMapHelper中的AddUninitializedValue函数与FStructProperty中的CopyCompleteValue相互配合就能写入。

void* MapValuePtr = TargetMapProperty->ContainerPtrToValuePtr<void*>(TargetMainObject, 0);

FScriptMapHelper MapHelper(TargetMapProperty, MapValuePtr);

if (FIntProperty* TargetInt32Property = CastField<FIntProperty>(TargetMapProperty->KeyProp))
{
    if (FStructProperty* TargetStructProperty = CastField<FStructProperty>(TargetMapProperty->ValueProp))
    {
        TMap<int32, UStruct*> TargetMap;
        FSubTestStruct* TestStruct1 = new FSubTestStruct();
        TestStruct1->StructInt32 = 11;
        FSubTestStruct* TestStruct2 = new FSubTestStruct();
        TestStruct2->StructInt32 = 22;
        FSubTestStruct* TestStruct3 = new FSubTestStruct();
        TestStruct3->StructInt32 = 33;
        TargetMap.Add(1, (UStruct*)TestStruct1);
        TargetMap.Add(2, (UStruct*)TestStruct2);
        TargetMap.Add(3, (UStruct*)TestStruct3);

        for (TTuple<int, UStruct*> SingleTargetMap : TargetMap)
        {
            int32 index = MapHelper.AddUninitializedValue();
            TargetInt32Property->CopyCompleteValue(MapHelper.GetKeyPtr(index), &SingleTargetMap.Key);
            TargetStructProperty->CopyCompleteValue(MapHelper.GetValuePtr(index), SingleTargetMap.Value);
            void* ResultInt = MapHelper.GetKeyPtr(index);
            void* ResultStruct = MapHelper.GetValuePtr(index);
            FSubTestStruct* ResultTestStruct = (FSubTestStruct*)ResultStruct;
        }

    }
}

四、参考资料

  1. 《InsideUE4》UObject(十一)类型系统构造-构造绑定链接
  2. 【UE4随笔】反射写代码之TMap
  3. UE4:反射系统获取UPROPERTY
  4. void*是怎样的存在?

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦