背景交代
因为是demo使用,不想接入太多第三库,但是网络通讯的需要的一个格式,方便两端通讯;
ue有一套json解释,所以计划使用json当作通讯流。
项目使用的消息体大致格式如下:
USTRUCT()
struct FTestStruct
{
GENERATED_BODY()
UPROPERTY()
FString Field1;
UPROPERTY()
FString Field2;
};
因为消息体的格式是自定义的,又不想每次都写Encode
和Decode
,最好的办法就是写一个通用的模板。
知识点
想要做到上述的方法,有几个点需要了解的:
- 模板函数限定
- 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
的反射,只需要使用USTRUCT
、UCLASS
就可以了(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了
注:当然还有其他方式,这里就是提供一种我学习中的测试!