unreal的几个数据类型的反射

背景交代

因为是demo使用,不想接入太多第三库,但是网络通讯的需要的一个格式,方便两端通讯;

ue有一套json解释,所以计划使用json当作通讯流。

项目使用的消息体大致格式如下:

USTRUCT()
struct FTestStruct
{
	GENERATED_BODY()
	UPROPERTY()
	FString Field1;
	UPROPERTY()
	FString Field2;
};

因为消息体的格式是自定义的,又不想每次都写EncodeDecode,最好的办法就是写一个通用的模板。

知识点

想要做到上述的方法,有几个点需要了解的:

  • 模板函数限定
  • unreal的反射

模板函数限定

这里就不细说,相关内容可以自行google;

// template<typename T, typename = typename TEnableIf<TIsClass<T>::Value>::Type> // 两种都可以!
template<typename T, typename = std::enable_if_t<std::is_class_v<T>, void> >
class NetUtility
{
public:
	static void Test(T* value, TArray<uint8>&  content)
	{
		auto file = value->StaticStruct()->ChildProperties;
		while (file != nullptr)
		{
			UE_LOG(LogDemo, Display, TEXT("%s"), *(file->GetName()));
			file = file->Next;
		}
	}
};

或者将消息体统一继承一个Parent struct,然后使用std::is_base_of<>去限定也可以!

unreal的反射

  • 获取反射类型
  • 成员变量(int float string)
  • Object子类属性
  • 修改和取值
  • 成员函数
  • 接口

获取反射类型

Unreal对满足其规则的类型定义,都会生成配套的反射;如果想使用unreal的反射,只需要使用USTRUCTUCLASS就可以了(GENERATED_BODY之类的肯定少不了,这里不细说!)。

这里用两个例子说明一下:

USTRUCT()
struct FTestStruct
{
	GENERATED_BODY()
	UPROPERTY()
	FString Field1;
	UPROPERTY()
	FString Field2;
};

UCLASS()
class UTestClass : public UObject
{
	GENERATED_BODY()
public:
	UPROPERTY()
	FString Field1;
	UPROPERTY()
	FString Field2;
};

unreal提供了一下几个获取反射类型的方法

ReflectedTypeAccessors.h

/*-----------------------------------------------------------------------------
C++ templated Static(Class/Struct/Enum) retrieval function prototypes.
-----------------------------------------------------------------------------*/

template<typename ClassType>	UClass*			StaticClass();
template<typename StructType>	UScriptStruct*	StaticStruct();
template<typename EnumType>		UEnum*			StaticEnum();

更加具体的在文件对应的XXXX.generated.h里有!

成员变量(int float string)

  • 通过ChildProperties获取所有的Property
  • 使用TFieldIterator<UProperty>构建迭代器
// 通过`ChildProperties`获取所有的`Property`
auto file = value->StaticStruct()->ChildProperties;
while (file != nullptr)
{
	UE_LOG(LogDemo, Display, TEXT("%s"), *(file->GetName())); // 变量名称
	file = file->Next;
}

// 使用`TFieldIterator<UProperty>`构建迭代器
for(TFieldIterator<FProperty> it(value->StaticStruct()); it;++it)
{
	UE_LOG(LogDemo, Display, TEXT("%s"), *(it->GetName()));
}

注意:如果是UCLASS,请使用StaticClass,UEnum类似

Object子类属性

在讲这个之前要提一下

/** Searches property link chain for a property with the specified name */
FProperty* FindPropertyByName(FName InName) const;

该方法可以直接从Ustruct或者UClass中找到指定的UProperty;

  • 通过变量名寻找UClass的对应属性
  • 将找到的属性转换为对应的Object*指针
UCLASS()
class UTestObject : public UObject
{
	GENERATED_BODY()
public:
	UPROPERTY()
	AActor* TestActor;
};

template<typename T, typename = typename TEnableIf<TIsClass<T>::Value>::Type>
// template<typename T, typename = std::enable_if_t<std::is_class_v<T>, void> >
class NetUtility
{
public:
	static void Test(T* value, TArray<uint8>&  content)
	{
		auto cls = value->StaticClass();
		for(TFieldIterator<FProperty> it(cls); it;++it)
		{
			auto _property = CastField<FObjectProperty>(*it);
			UE_LOG(LogDemo, Display, TEXT("%s"), *_property->GetName());
		}
	}
};

除了FObjectProperty还有很多。

DefineUPropertyMacros.h中比较全(虽然都是对DEPRECATED_MACRO的重定义,但是全呀!)!

到这里估计的时候我就遇到一个问题:

能不能直接识别判定是什么类型后再指定CastField

Field.h文件中有这样的代码

template<typename FieldType>
FORCEINLINE FieldType* CastField(FField* Src)
{
	return Src && Src->HasAnyCastFlags(FieldType::StaticClassCastFlagsPrivate()) ? static_cast<FieldType*>(Src) : nullptr;
}

template<typename FieldType>
FORCEINLINE const FieldType* CastField(const FField* Src)
{
	return Src && Src->HasAnyCastFlags(FieldType::StaticClassCastFlagsPrivate()) ? static_cast<const FieldType*>(Src) : nullptr;
}

已经有类型判定了~

修改和取值

UCLASS()
class UTestObject : public UObject
{
	GENERATED_BODY()
public:
	UPROPERTY()
	AActor* ActorValue;
	UPROPERTY()
	FString StringValue;
	UPROPERTY()
	float FloatValue;
};

template<typename T, typename = typename TEnableIf<TIsClass<T>::Value>::Type>
// template<typename T, typename = std::enable_if_t<std::is_class_v<T>, void> >
class NetUtility1
{
public:
	static void Test(T* value, TArray<uint8>&  content)
	{
		auto cls = value->StaticClass();
		for(TFieldIterator<FProperty> it(cls); it;++it)
		{
			auto _objectProperty = CastField<FObjectProperty>(*it);
			if (_objectProperty)
			{
				void* param = _objectProperty->ContainerPtrToValuePtr<void*>(value);
				AActor* pValue = Cast<AActor>(_objectProperty->GetPropertyValue(param));
				UE_LOG(LogDemo, Display, TEXT("before modify %s : %s"), *(it->GetName()), *pValue->GetName());
				_objectProperty->SetPropertyValue(param, NewObject<AActor>());
				pValue = Cast<AActor>(_objectProperty->GetPropertyValue(param));
				UE_LOG(LogDemo, Display, TEXT("after modify %s : %s"), *(it->GetName()), *pValue->GetName());
				continue;
			}
			auto _stringProperty = CastField<FStrProperty>(*it);
			if (_stringProperty)
			{
				void* param = _stringProperty->ContainerPtrToValuePtr<void*>(value);
				FString pValue = _stringProperty->GetPropertyValue(param);
				UE_LOG(LogDemo, Display, TEXT("before modify %s : %s"), *(it->GetName()), *pValue);
				_stringProperty->SetPropertyValue(param, TEXT("CCCCC"));
				pValue = _stringProperty->GetPropertyValue(param);
				UE_LOG(LogDemo, Display, TEXT("after modify %s : %s"), *(it->GetName()), *pValue);
				continue;
			}
			auto _floatProperty = CastField<FFloatProperty>(*it);
			if (_floatProperty)
			{
				void* param = _floatProperty->ContainerPtrToValuePtr<void*>(value);
				auto pValue = _floatProperty->GetPropertyValue_InContainer(param, 0);
				
				UE_LOG(LogDemo, Display, TEXT("before modify %s : %f"), *(it->GetName()), pValue);
				_floatProperty->SetPropertyValue(param, 444.0f);
				pValue = _floatProperty->GetPropertyValue(param);
				UE_LOG(LogDemo, Display, TEXT("after modify %s : %f"), *(it->GetName()), pValue);
				continue;
			}
		}
	}
};

成员函数

成员函数的获取和属性有点类似

  • 使用TFieldIterator<UProperty>构建迭代器
for(TFieldIterator<UFunction> func(value->StaticClass()); func;++func)
{}

如果是还需要获取函数的参数列表:

for(TFieldIterator<UFunction> func(value->StaticClass()); func;++func)
{
	for(TFieldIterator<FProperty> args(*func); args;++args)
	{}
}

接口

成员函数的获取和属性有点类似

  • 通过ChildProperties获取所有的Property
  • 使用TFieldIterator<UProperty>构建迭代器
// 通过`Interfaces`获取所有的`Interface`
auto interfaceArray = value->StaticStruct()->Interfaces;
for (auto interface : interfaceArray)
{
}

// 使用`TFieldIterator<FInterfaceProperty>`构建迭代器
for(TFieldIterator<FInterfaceProperty> it(value->StaticStruct()); it;++it)
{
}

函数调用

UFunction的调用基本就是UFunction的使用了,这里就不细说了

UFunction->Invoke();

到此,基本就可以直接反射出模板函数中的消息体的每个UProperty的值和定义的变量名称了;从而就能生成json了

注:当然还有其他方式,这里就是提供一种我学习中的测试!