--- title: Ue4 c++ UProperty反射 PostEditChangeProperty date: 2022-12-09 13:40:28 excerpt: tags: UObject rating: ⭐⭐⭐ --- ## 反射系统 https://ikrima.dev/ue4guide/engine-programming/uobject-reflection/uobject-reflection/ ## Property类型判断 - UStructProperty:结构体 - UMapProperty:TMap - UArrayProperty:TArray 属性判断是否是数组或是Map ```c++ FProperty* PropertyThatChanged = PropertyChangedEvent.Property; if ( PropertyThatChanged != nullptr ) {     if (PropertyThatChanged->IsA(FArrayProperty::StaticClass()))     {         FArrayProperty* ArrayProp = CastField(PropertyThatChanged);         FProperty* InnerProp = ArrayProp->Inner;         if (InnerProp->IsA(FStructProperty::StaticClass()))         {             const FToonRampData* ToonRampData = ArrayProp->ContainerPtrToValuePtr(InnerProp, 0);             if(ToonRampData)             {             }         }     }else if(PropertyThatChanged->IsA(FMapProperty::StaticClass()))     {         FArrayProperty* ArrayProp = CastField(PropertyThatChanged);         FProperty* InnerProp = ArrayProp->Inner;         if (InnerProp->IsA(FStructProperty::StaticClass()))         {             const FToonRampData* ToonRampData = ArrayProp->ContainerPtrToValuePtr(InnerProp, 0);             if(ToonRampData)             {             }         }     }     } ``` ## 属性变化回调函数 ```c# virtual void EditorApplyTranslation(const FVector& DeltaTranslation, bool bAltDown, bool bShiftDown, bool bCtrlDown) override; virtual void EditorApplyRotation(const FRotator& DeltaRotation, bool bAltDown, bool bShiftDown, bool bCtrlDown) override; virtual void EditorApplyScale(const FVector& DeltaScale, const FVector* PivotLocation, bool bAltDown, bool bShiftDown, bool bCtrlDown) override; virtual void PostEditMove(bool bFinished) override; virtual void PostEditComponentMove(bool bFinished) override; ``` # 没有Struct()却可以在蓝图找到类型问题笔记 ## Traits 大概率是这个 ```c++ template<> struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 {     enum     {         WithIdenticalViaEquality = true,         WithNoInitConstructor = true,         WithZeroConstructor = true,     }; }; IMPLEMENT_STRUCT(Box2D); ``` ## BlueprintCompilerCppBackendValueHelper.cpp ```c++ bool FEmitDefaultValueHelper::SpecialStructureConstructor(const UStruct* Struct, const uint8* ValuePtr, /*out*/ FString* OutResult) {     ...     if (TBaseStructure::Get() == Struct)     {         if (OutResult)         {             const FBox2D* Box2D = reinterpret_cast(ValuePtr);             *OutResult = FString::Printf(TEXT("CreateFBox2D(FVector2D(%s, %s), FVector2D(%s, %s), %s)")                 , *FEmitHelper::FloatToString(Box2D->Min.X)                 , *FEmitHelper::FloatToString(Box2D->Min.Y)                 , *FEmitHelper::FloatToString(Box2D->Max.X)                 , *FEmitHelper::FloatToString(Box2D->Max.Y)                 , Box2D->bIsValid ? TEXT("true") : TEXT("false"));         }         return true;     }     ... } struct FStructAccessHelper_StaticData {     TMap BaseStructureAccessorsMap;     TMap SupportsDirectNativeAccessMap;     TArray NoExportTypesWithDirectNativeFieldAccess;     static FStructAccessHelper_StaticData& Get()     {         static FStructAccessHelper_StaticData StaticInstance;         return StaticInstance;     } private:     FStructAccessHelper_StaticData()     {         // These are declared in Class.h; it's more efficient to access these native struct types at runtime using the specialized template functions, so we list them here.         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         MAP_BASE_STRUCTURE_ACCESS(TBaseStructure::Get());         {             // Cache the known set of noexport types that are known to be compatible with emitting native code to access fields directly.             TArray Paths;             GConfig->GetArray(TEXT("BlueprintNativizationSettings"), TEXT("NoExportTypesWithDirectNativeFieldAccess"), Paths, GEditorIni);             for (FString& Path : Paths)             {                 NoExportTypesWithDirectNativeFieldAccess.Add(FSoftClassPath(Path));             }         }     } } ``` ## 大钊的文章中有相似的代码 https://zhuanlan.zhihu.com/p/26019216 Struct的收集 对于Struct,我们先来看上篇里生成的代码: ```c++ static FCompiledInDeferStruct Z_CompiledInDeferStruct_UScriptStruct_FMyStruct(FMyStruct::StaticStruct, TEXT("/Script/Hello"), TEXT("MyStruct"), false, nullptr, nullptr);  //延迟注册 static struct FScriptStruct_Hello_StaticRegisterNativesFMyStruct {     FScriptStruct_Hello_StaticRegisterNativesFMyStruct()     {         UScriptStruct::DeferCppStructOps(FName(TEXT("MyStruct")),new UScriptStruct::TCppStructOps);     } } ScriptStruct_Hello_StaticRegisterNativesFMyStruct;   ``` https://zhuanlan.zhihu.com/p/59553490 ICppStructOps的作用 很多朋友在看源码的时候,可能会对UScriptStruct里定义的ICppStructOps类以及模板子类`TCppStructOps`感到疑惑。其实它们是C++的一种常见的架构模式,用一个虚函数基类定义一些公共操作,再用一个具体模板子类来实现,从而既可以保存类型,又可以有公共操作接口。 针对于UE4这里来说,ICppStructOps就定义了这个结构的一些公共操作。而探测这个C++结构的一些特性就交给了`TCppStructOps`类里的`TStructOpsTypeTraits`。一些C++结构的信息不能通过模板探测出来的,就需要我们手动标记提供了,所以具体的代码是: ```c++ template struct TStructOpsTypeTraitsBase2 {     enum     {         WithZeroConstructor = false, // 0构造,内存清零后就可以了,说明这个结构的默认值就是0         WithNoInitConstructor = false, // 有个ForceInit的参数的构造,用来专门构造出0值结构来         WithNoDestructor = false, // 是否没有结构有自定义的析构函数, 如果没有析构的话,DestroyStruct里面就可以省略调用析构函数了。默认是有的。结构如果是pod类型,则肯定没有析构。         WithCopy = !TIsPODType::Value, // 是否结构有自定义的=赋值函数。如果没有的话,在CopyScriptStruct的时候就只需要拷贝内存就可以了         WithIdenticalViaEquality = false, // 用==来比较结构         WithIdentical = false, // 有一个自定义的Identical函数来专门用来比较,和WithIdenticalViaEquality互斥         WithExportTextItem = false, // 有一个ExportTextItem函数来把结构值导出为字符串         WithImportTextItem = false, // 有一个ImportTextItem函数把字符串导进结构值         WithAddStructReferencedObjects = false, // 有一个AddStructReferencedObjects函数用来添加结构额外的引用对象         WithSerializer = false, // 有一个Serialize函数用来序列化         WithStructuredSerializer = false, // 有一个结构结构Serialize函数用来序列化         WithPostSerialize = false, // 有一个PostSerialize回调用来在序列化后调用         WithNetSerializer = false, // 有一个NetSerialize函数用来在网络复制中序列化         WithNetDeltaSerializer = false, // 有一个NetDeltaSerialize函数用来在之前NetSerialize的基础上只序列化出差异来,一般用在TArray属性上进行优化         WithSerializeFromMismatchedTag = false, // 有一个SerializeFromMismatchedTag函数用来处理属性tag未匹配到的属性值,一般是在结构进行升级后,但值还是原来的值,这个时候用来把旧值升级到新结构时使用         WithStructuredSerializeFromMismatchedTag = false, // SerializeFromMismatchedTag的结构版本         WithPostScriptConstruct = false,// 有一个PostScriptConstruct函数用在蓝图构造脚本后调用         WithNetSharedSerialization = false, // 指明结构的NetSerialize函数不需要用到UPackageMap     }; }; template struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 { }; ``` 举个小例子,假如你看到编辑器里某个属性,想在C++里去修改它的值,结果发现它不是public的,甚至有可能连头文件都是private的,这个时候如果对类型系统结构理解不深的人可能就放弃了,但懂的人就知道可以通过这个对象遍历UProperty来查找到这个属性从而修改它。 还有一个例子是如果你做了一个插件,调用了引擎编辑器本身的Details面板属性,但又想隐藏其中的一些字段,这个时候如果不修改引擎往往是难以办到的,但是如果知道了属性面板里的属性其实也都是一个个UProperty来的,这样你就可以通过对象路径获得这个属性,然后开启关闭它的某些Flags来达成效果。这也算是一种常规的Hack方式。 ## 《InsideUE4》UObject(十三)类型系统-反射实战 https://zhuanlan.zhihu.com/p/61042237 #### 获取类型对象 如果想获取到程序里定义的所有的class,方便的方法是: ```c++ TArray result; GetObjectsOfClass(UClass::StaticClass(), result);   //获取所有的class和interface GetObjectsOfClass(UEnum::StaticClass(), result);   //获取所有的enum GetObjectsOfClass(UScriptStruct::StaticClass(), result);   //获取所有的struct ``` GetObjectsOfClass是UE4已经写好的一个很方便的方法,可以获取到属于某个UClass*下面的所有对象。因此如果用UClass::StaticClass()本身,就可以获得程序里定义的所有class。值得注意的是,UE4里的接口是有一个配套的UInterface对象来存储元数据信息,它的类型也是用UClass*表示的,所以也会获得interface。根据前文,enum会生成UEnum,struct会生成UScriptStruct,所以把参数换成UEnum::StaticClass()就可以获得所有的UEnum*对象了,UScriptStruct::StaticClass()就是所有的UScriptStruct*了,最后就可以根据这些类型对象来反射获取类型信息了。 而如果要精确的根据一个名字来查找某个类型对象,就可以用UE4里另一个方法: ```c++ template< class T > inline T* FindObject( UObject* Outer, const TCHAR* Name, bool ExactClass=false ) {     return (T*)StaticFindObject( T::StaticClass(), Outer, Name, ExactClass ); } UClass* classObj=FindObject(ANY_PACKAGE,"MyClass");   //获得表示MyClass的UClass* ``` #### 遍历字段 在获取到了一个类型对象后,就可以用各种方式去遍历查找内部的字段了。为此,UE4提供了一个方便的迭代器`TFieldIterator`,可以通过它筛选遍历字段。 ```c++ const UStruct* structClass; //任何复合类型都可以 //遍历属性 for (TFieldIterator i(structClass); i; ++i) {     UProperty* prop=*i; } //遍历函数 for (TFieldIterator i(structClass); i; ++i) {     UFunction* func=*i;     //遍历函数的参数     for (TFieldIterator i(func); i; ++i)     {         UProperty* param=*i;         if( param->PropertyFlags & CPF_ReturnParm ) //这是返回值         {         }     } } //遍历接口 const UClass* classObj; //只有UClass才有接口 for (const FImplementedInterface& ii : classObj->Interfaces) {     UClass* interfaceClass = ii.Class; } //遍历枚举 const UEnum* enumClass; for (int i = 0; i < enumClass->NumEnums(); ++i) {     FName name = enumClass->GetNameByIndex(i);     int value = enumClass->GetValueByIndex(i); } //遍历元数据 #if WITH_METADATA const UObject* obj;//可以是任何对象,但一般是UField才有值 UMetaData* metaData = obj->GetOutermost()->GetMetaData(); TMap* keyValues = metaData->GetMapForObject(obj); if (keyValues != nullptr&&keyValues->Num() > 0) {     for (const auto& i : *keyValues)     {         FName key=i.Key;         FString value=i.Value;     } } #endif //查找属性 UProperty* UStruct::FindPropertyByName(FName InName) const {     for (UProperty* Property = PropertyLink; Property != NULL; Property = Property->PropertyLinkNext)     {         if (Property->GetFName() == InName)         {             return Property;         }     }     return NULL; } //查找函数 UFunction* UClass::FindFunctionByName(FName InName, EIncludeSuperFlag::Type IncludeSuper) const; ``` #### 查看继承 ```c++ //得到类型对象后,也可以遍历查看它的继承关系。 遍历继承链条: const UStruct* structClass; //结构和类 TArray classNames; classNames.Add(structClass->GetName()); UStruct* superClass = structClass->GetSuperStruct(); while (superClass) {     classNames.Add(superClass->GetName());     superClass = superClass->GetSuperStruct(); } FString str= FString::Join(classNames, TEXT("->")); //会输出MyClass->UObject //那反过来,如果想获得一个类下面的所有子类,可以这样: const UClass* classObj; //结构和类 TArray result; GetDerivedClasses(classObj, result, false); //函数原型是 void GetDerivedClasses(UClass* ClassToLookFor, TArray& Results, bool bRecursive); //那么怎么获取实现了某个接口的所有子类呢? TArray result; GetObjectsOfClass(UClass::StaticClass(), result); TArray classes; for (UObject* obj : result) {     UClass* classObj = Cast(obj);     if (classObj->ImplementsInterface(interfaceClass))//判断实现了某个接口     {         classes.Add(classObj);     } } ``` #### 获取设置属性值 ```c++ template ValueType* UProperty::ContainerPtrToValuePtr(void* ContainerPtr, int32 ArrayIndex = 0) const {     return (ValueType*)ContainerVoidPtrToValuePtrInternal(ContainerPtr, ArrayIndex); } template ValueType* UProperty::ContainerPtrToValuePtr(UObject* ContainerPtr, int32 ArrayIndex = 0) const {     return (ValueType*)ContainerVoidPtrToValuePtrInternal(ContainerPtr, ArrayIndex); } void* UProperty::ContainerVoidPtrToValuePtrInternal(void* ContainerPtr, int32 ArrayIndex) const {     //check...     return (uint8*)ContainerPtr + Offset_Internal + ElementSize * ArrayIndex; } void* UProperty::ContainerUObjectPtrToValuePtrInternal(UObject* ContainerPtr, int32 ArrayIndex) const {     //check...     return (uint8*)ContainerPtr + Offset_Internal + ElementSize * ArrayIndex; } //获取对象或结构里的属性值地址,需要自己转换成具体类型 void* propertyValuePtr = property->ContainerPtrToValuePtr(object); //包含对象引用的属性可以获得对象 UObject* subObject = objectProperty->GetObjectPropertyValue_InContainer(object); //也因为获取到的是存放属性值的指针地址,所以其实也就可以*propertyValuePtr=xxx;方便的设置值了。当然如果是从字符串导入设置进去,UE4也提供了两个方法来导出导入: //导出值 virtual void ExportTextItem( FString& ValueStr, const void* PropertyValue, const void* DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope = NULL ) const; //使用 FString outPropertyValueString; property->ExportTextItem(outPropertyValueString, property->ContainerPtrToValuePtr(object), nullptr, (UObject*)object, PPF_None); //导入值 const TCHAR* UProperty::ImportText( const TCHAR* Buffer, void* Data, int32 PortFlags, UObject* OwnerObject, FOutputDevice* ErrorText = (FOutputDevice*)GWarn ) const; //使用 FString valueStr; prop->ImportText(*valueStr, prop->ContainerPtrToValuePtr(obj), PPF_None, obj); ``` #### 反射调用函数 ```c++ //方法原型 int32 UMyClass::Func(float param1); UFUNCTION(BlueprintCallable) int32 InvokeFunction(UObject* obj, FName functionName,float param1) {     struct MyClass_Func_Parms   //定义一个结构用来包装参数和返回值,就像在gen.cpp里那样     {         float param1;         int32 ReturnValue;     };     UFunction* func = obj->FindFunctionChecked(functionName);     MyClass_Func_Parms params;     params.param1=param1;     obj->ProcessEvent(func, ¶ms);     return params.ReturnValue; } //使用 int r=InvokeFunction(obj,"Func",123.f); ``` ProcessEvent也是UE4里事先定义好的非常方便的函数,内部会自动的处理蓝图VM的问题。当然,更底层的方法也可以是: ```c++ //调用1 obj->ProcessEvent(func, ¶ms); //调用2 FFrame frame(nullptr, func, ¶ms, nullptr, func->Children); obj->CallFunction(frame, ¶ms + func->ReturnValueOffset, func); //调用3 FFrame frame(nullptr, func, ¶ms, nullptr, func->Children); func->Invoke(obj, frame, ¶ms + func->ReturnValueOffset); ``` #### 运行时修改类型 让我们继续扩宽一下思路,之前已经详细讲解过了各大类型对象的构造过程,最后常常都是到UE4CodeGen_Private里的调用。既然我们已经知道了它运行的逻辑,那我们也可以仿照着来啊!我们也可以在常规的类型系统注册流程执行完之后,在游戏运行的半途过程中,动态的去修改类型甚至注册类型,因为说到底UE4编辑器也就是一个特殊点的游戏而已啊!这种方式有点类似C#的emit的方式,用代码去生成代码然后再编译。这些方式理论上都是可以通的,我来提供一些思路用法,有兴趣的朋友可以自己去实现下,代码贴出来就太长了。 1. 修改UField的MetaData信息,其实可以改变字段在编辑器中的显示信息。MetaData里有哪些字段,可以在ObjectMacros.h中自己查看。 2. 动态修改UField的相应的各种Flags数据,比如PropertyFlags,StructFlags,ClassFlags等,可以达成在编辑器里动态改变其显示行为的效果。 3. 动态添加删除UEnum对象里面的Names字段,就可以动态给enum添加删除枚举项了。 4. 动态地给结构或类添加反射属性字段,就可以在蓝图内创建具有不定字段的结构了。当然前提是在结构里预留好属性存放的内存,这样UProperty的Offset才有值可指向。这么做现在想来好像也不知道能用来干嘛。 5. 同属性一样,其实参照对了流程,也可以动态的给蓝图里暴露函数。有时候这可以达成某种加密保护的奇效。 6. 可以动态的注册新结构,动态的构造出来相应的UScriptStruct其实就可以了。 7. 动态注册新类其实也是可以的,只不过UClass的构造稍微要麻烦点,不过也没麻烦到哪去,有需求了就自然能照着源码里的流程自己实现一个流程出来。 8. 再甚至,其实某种程度上的用代码动态创建蓝图节点,填充蓝图VM指令其实也是可行的。只不过想了想好像一般用不着上这种大手术。