acecase 发表于 2021-11-17 21:36

【UE4/5】利用反射系统写代码:根据函数名调用函数

背景:

笔者在基于Unreal开发数字孪生开发平台时,为了方便设计师快速开发数字孪生应用,将一些常用的函数进行了封装,供设计师在Runtime通过可视化的形式进行使用;为了实现这个目的,特地开了一个大坑,做了一个Runtime的蓝图(为了与unreal的蓝图区分,下文简称RuntimeBP)。其中一项功能就是:
蓝图开发人员在unreal蓝图中开发一些Functions或者Event


根据名字,利用反射系统,找到对应的UFunction* 以及该Function对应的入参和出参的名字和类型RuntimeBP系统根据入参和出参的名字和类型,分别自动生成对应的pin脚,构成一个RuntimeBP节点


在RuntimeBP系统执行到该节点时,根据名字再次找到该UFunction,并且根据pin脚实时更新后的值执行该UFunction
其中主要要解决两个问题:
获取到UFunction*对应的入参和出参,这个比较简单,后面贴一下代码根据名字调用一个函数,参数的数量和类型是runtime时才知道的
第一个问题:
直接根据Function的NumParms和链表式的property链即可获取
UFunction* func = inClass->FindFunctionByName(FunctionFName);
                if (func && (func->ParmsSize > 0))
                {
                        int32 param_idx = 0;
                        FField* ChildProperty = func->ChildProperties;
                        TArray<FGPParameterInfo> infoList;
                        while (param_idx < func->NumParms)
                        {
                                FGPParameterInfo info;//记录了参数的名字和类型
                                GetPropertyInfo(Cast<FProperty>(ChildProperty), info);
                                ChildProperty = ChildProperty->Next;
                                infoList.Add(info);
                                param_idx++;
                        }
}
               
第二个问题:
参考 @大钊 的文章
根据名字调用UFunction的几种方式有:
定义一个结构体来包装参数和返回值:
struct MyClass_Func_Parms   
    {
      float param1;
      int32 ReturnValue;
    };
    UFunction* func = obj->FindFunctionChecked(functionName);
    MyClass_Func_Parms params;
    params.param1=param1;
    obj->ProcessEvent(func, &params);2. 实现一个模板函数,大钊文章里给出了一个模板函数,很方便的可以在蓝图中调用,见上述文章链接
可以看到这两种方式用于实现我们的需求还是有一些棘手的,实际上我们可以根据pin脚拿到所有的变量值,却不好构造一个struct或者一个模板来快速方便的调用函数。
为了应对这个问题,我探索了两种方案来解决这个问题:
方案1:
分析一下方式1,实际上就是将参数组装成了一个结构体,并将这块内存传给ProcessEvent,那么我们能不能手动构造这块内存,来达到同样的效果呢?答案是可以的,这里我拿到pin脚的值后,直接将值赋给了Function中对应的Property,然后将所有参数对应的Property对应的内存拼接起来,当作ProcessEvent的参数,这样做有个好处就是自己不用去考虑内存对齐的问题,Property种已经含有了这个信息
代码如下:
        TArray<uint8> ParamsBuffer;
        for (int i = 0; i < func->NumParms; i++)
        {
                FProperty* Property = func->FindPropertyByName(FName(*InputPins.PinName));//根据pin脚的名字,找到对应的property
                TArray<uint8> PropertyBuffer;
                PropertyBuffer.SetNumZeroed(Property->GetSize());
                switch (InputPins.VariableType)
                {
                case EVariableTypes::Bool:
                {
                        auto result = GetInputValue(Index + i, Bool);//得到pin脚的value
                        PropertyBuffer = result;
                        break;
                }
                case EVariableTypes::Float:
                {
                        auto result = GetInputValue(Index + i, Float);
                        FFloatProperty* FloatProperty = Cast<FFloatProperty>(Property);
                        void* valuePtrOfStruct = FloatProperty->ContainerPtrToValuePtr<void>(PropertyBuffer.GetData());
                        FloatProperty->SetPropertyValue(valuePtrOfStruct, result);
                        break;
                }
                case EVariableTypes::Int:
                {
                        auto result = GetInputValue(Index + i, Int);
                        FIntProperty* IntProperty = Cast<FIntProperty>(Property);
                        void* valuePtrOfStruct = IntProperty->ContainerPtrToValuePtr<void>(PropertyBuffer.GetData());
                        IntProperty->SetPropertyValue(valuePtrOfStruct, result);
                        break;
                }
                ...
                }
                ParamsBuffer.Append(PropertyBuffer);
        }
        owned_actor->ProcessEvent(func, ParamsBuffer.GetData());
方案2:
其实类似的需求非常的常见,如各种lua、js脚本语言调用UFunction,并传参过来,这里贴一段WebBrowser调用UFunction,将参数动态的组装起来的例子,其利用了Unreal自带的FStructDeserializer::Deserialize函数将反序列化后端IStructDeserializerBackend根据传入的struct类型信息,构造出一个指向要反序列化的结构的指针,这里UFunction本身就是继承自UStruct,直接当参数传入即可。
if (ParamsSize > 0)
        {
                // Convert cef argument list to a dictionary, so we can use FStructDeserializer to convert it for us
                CefRefPtr<CefDictionaryValue> NamedArgs = CefDictionaryValue::Create();
                int32 CurrentArg = 0;
                CefRefPtr<CefListValue> CefArgs = MessageArguments->GetList(3);
                for ( TFieldIterator<FProperty> It(Function); It; ++It )
                {
                        FProperty* Param = *It;
                        if (Param->PropertyFlags & CPF_Parm)
                        {
                                if (Param->PropertyFlags & CPF_ReturnParm)
                                {
                                        ReturnParam = Param;
                                }
                                else
                                {
                                        FStructProperty *StructProperty = CastField<FStructProperty>(Param);
                                        if (StructProperty && StructProperty->Struct->IsChildOf(FLancelotWebJSResponse::StaticStruct()))
                                        {
                                                PromiseParam = Param;
                                        }
                                        else
                                        {
                                                CopyContainerValue(NamedArgs, CefArgs, CefString(TCHAR_TO_WCHAR(*GetBindingName(Param))), CurrentArg);
                                                CurrentArg++;
                                        }
                                }
                        }
                }

                // UFunction is a subclass of UStruct, so we can treat the arguments as a struct for deserialization
                Params.AddUninitialized(ParamsSize);
                Function->InitializeStruct(Params.GetData());
                FCEFJSStructDeserializerBackend Backend = FCEFJSStructDeserializerBackend(SharedThis(this), NamedArgs);
                FStructDeserializer::Deserialize(Params.GetData(), *Function, Backend);
        }

        if (PromiseParam)
        {
                FLancelotWebJSResponse* PromisePtr = PromiseParam->ContainerPtrToValuePtr<FLancelotWebJSResponse>(Params.GetData());
                if (PromisePtr)
                {
                        *PromisePtr = FLancelotWebJSResponse(SharedThis(this), ResultCallbackId);
                }
        }

        UE_LOG(LogTemp, Log, TEXT("FCEFJSScripting::HandleExecuteUObjectMethodMessage %s ready to start, Params.size = %d"), *MethodName.ToString(), Params.Num());

        Object->ProcessEvent(Function, Params.GetData());
最后,我觉得应该有更简单的方式来达到同样的需求,毕竟unreal 在编辑器中允许用户自定义struct,那么一定有方法组装strcut的成员变量,笔者源码阅读有限,还未找到对应的源码0.0,有更好的方法,还望不吝赐教~
【注:在做的平台是类似TwinMotion那种,经unreal开发, 但是用户们不会直接碰到unreal的开发,数字孪生应用又少不了编排各种逻辑,所以才诞生了runtimeBP这个想法,以提供更加简单便捷的节点给到设计师使用】

Doris232 发表于 2021-11-17 21:36

类似51的产品吧

RhinoFreak 发表于 2021-11-17 21:44

超越51哈哈哈哈
页: [1]
查看完整版本: 【UE4/5】利用反射系统写代码:根据函数名调用函数