private unsafe void HandleInvokeFunctionFromNative(IntPtr obj, FFrame stack, IntPtr result, UFunction.FuncInvokerManaged managedFunctionInvoker) { IntPtr function = stack.CurrentNativeFunction; IntPtr paramsBuffer = stack.Locals; if (managedFunctionInvoker != null) { // Call the managed function invoker which will marshal the params from the native params buffer and then call the // target managed function managedFunctionInvoker(paramsBuffer, obj); } // Copy out params back from the locals buffer if (Native_UFunction.HasAnyFunctionFlags(function, EFunctionFlags.HasOutParms)) { // This assumes that UProperty will be itterated in the exact same order as the caller created stack->OutParms // (we could iterate stack->OutParms until nullptr but it isn't null terminated on release builds) FOutParmRec *outParms = stack.OutParmsPtr; foreach (IntPtr paramProp in new NativeReflection.NativeFieldIterator(Runtime.Classes.UProperty, function, false)) { EPropertyFlags paramFlags = Native_UProperty.GetPropertyFlags(paramProp); if ((paramFlags & EPropertyFlags.OutParm) == EPropertyFlags.OutParm && (paramFlags & EPropertyFlags.ConstParm) != EPropertyFlags.ConstParm) { Debug.Assert(outParms->Property == paramProp); // - REMOVING the DestroyValue call. The issue with this DestroyValue call is that PropAddr // and paramsBuffer can (will?) be refering to the same data which we should have set in // managedFunctionInvoker. So a call to DestroyValue would be destroying the data we want! // Though if PropAddr is for some reason still holding onto unknown memory this will leak // memory and we will need to investigate further. // - The "refering to the same data" is because of the params Memcpy in UObject::ProcessEvent. // They wont have the same address but their inital data will be the same and as such // things like FString data pointer would be pointing to the same address. // - We may still need to call DestroyValue in the BP/VM code path above. // Destroy the existing memory (this assumed the existing memory is valid or at least memzerod) //Native_UProperty.DestroyValue(paramProp, outParms->PropAddr); // A raw memcpy should be OK since the managed invoker should have set this memory appropriately. FMemory.Memcpy(outParms->PropAddr, paramsBuffer + Native_UProperty.GetOffset_ForUFunction(paramProp), Native_UProperty.Get_ElementSize(paramProp));// Should be ArrayDim*ElementSize but ArrayDim should always be 1 for params outParms = outParms->NextOutParamPtr; } } } // We assume that the caller will clean up the memory held by stack->Locals so we don't iterate over the // DestructorLink as we do in HandleInvokeFunctionFromBP. HandleInvokeFunctionFromBP needs to as it creates // copies of data when calling stack->Step() which don't occur here as we use the existing stack->Locals buffer. }
private unsafe void HandleInvokeFunctionFromBP(IntPtr obj, FFrame *stack, IntPtr result, UFunction.FuncInvokerManaged managedFunctionInvoker) { // NOTE: ScriptCore.cpp uses PropertiesSize instead of ParamsSize. Is it ever any different? (it says alignment // may make them different) If it is different we should probably use PropertiesSize (including in generated code / // IL) as ScriptCore.cpp uses a memcpy of our memory. Debug.Assert(Native_UStruct.Get_PropertiesSize(stack->CurrentNativeFunction) == Native_UFunction.Get_ParmsSize(stack->CurrentNativeFunction)); IntPtr function = stack->CurrentNativeFunction; int paramsSize = Native_UFunction.Get_ParmsSize(function); int numParams = Native_UFunction.Get_NumParms(function); bool hasOutParams = Native_UFunction.HasAnyFunctionFlags(function, EFunctionFlags.HasOutParms); IntPtr *outParamsBufferPtr = stackalloc IntPtr[numParams]; byte * paramsBufferPtr = stackalloc byte[paramsSize]; IntPtr paramsBuffer = (IntPtr)paramsBufferPtr; // We could skip this memzero as stackalloc will (always?) zero memory even though the spec states // "The content of the newly allocated memory is undefined." // https://github.com/dotnet/coreclr/issues/1279 FMemory.Memzero(paramsBuffer, paramsSize); if (hasOutParams) { int paramIndex = 0; foreach (IntPtr param in new NativeReflection.NativeFieldIterator(Runtime.Classes.UProperty, function, false)) { // Not required but using for Debug.Assert() when getting the value stack->MostRecentPropertyAddress = IntPtr.Zero; stack->Step(stack->Object, paramsBuffer + Native_UProperty.GetOffset_ForUFunction(param)); outParamsBufferPtr[paramIndex] = stack->MostRecentPropertyAddress; if (Native_UProperty.HasAnyPropertyFlags(param, EPropertyFlags.ReturnParm)) { // This should be UObject::execEndFunctionParms which will just do "stack->Code--;" for allowing // the caller to use PFINISH aftwards outParamsBufferPtr[paramIndex] = result; } paramIndex++; } } else { foreach (IntPtr param in new NativeReflection.NativeFieldIterator(Runtime.Classes.UProperty, function, false)) { stack->Step(stack->Object, paramsBuffer + Native_UProperty.GetOffset_ForUFunction(param)); } } stack->PFinish();// Skip EX_EndFunctionParms // Call the managed function invoker which will marshal the params from the native params buffer and then call the // target managed function managedFunctionInvoker(paramsBuffer, obj); // Copy out params from the temp buffer //if (hasOutParams) // 4.20 DestructorLink change (see below) { int paramIndex = 0; foreach (IntPtr paramProp in new NativeReflection.NativeFieldIterator(Runtime.Classes.UProperty, function, false)) { EPropertyFlags paramFlags = Native_UProperty.GetPropertyFlags(paramProp); if ((paramFlags & EPropertyFlags.OutParm) == EPropertyFlags.OutParm && (paramFlags & EPropertyFlags.ConstParm) != EPropertyFlags.ConstParm) { Debug.Assert(outParamsBufferPtr[paramIndex] != IntPtr.Zero); // - See "REMOVING the DestroyValue call" below. // Destroy the existing memory (this assumed the existing memory is valid or at least memzerod) //Native_UProperty.DestroyValue(paramProp, outParamsBufferPtr[paramIndex]); // A raw memcpy should be OK since the managed invoker should have set this memory appropriately. FMemory.Memcpy(outParamsBufferPtr[paramIndex], paramsBuffer + Native_UProperty.GetOffset_ForUFunction(paramProp), Native_UProperty.Get_ElementSize(paramProp));// Should be ArrayDim*ElementSize but ArrayDim should always be 1 for params } else { // 4.20 the original DestructorLink code doesn't work as expected (did it ever work?). DestructorLink seems to only be // used on delegate functions (e.g. /Script/Engine.ActorOnClickedSignature__DelegateSignature). // - For now call destroy everything other than out params (is this safe?) // - TODO: Replace this with custom Stack.Code stepping as described above? Native_UProperty.DestroyValue_InContainer(paramProp, paramsBuffer); } paramIndex++; } } // Parameters are copied when calling stack->Step(). We are responsible for destroying non-blittable types // which were copied (FString, TArray, etc). For C++ this works out well due to the copy constructors etc. // // Example where an FString is constructed from stack->Step(): // UObject::execStringConst(...) { *(FString*)RESULT_PARAM = (ANSICHAR*)Stack.Code; } // // For C# it might be better if we reimplemented all of the IMPLEMENT_VM_FUNCTION functions to reduce the amount // of copying as we currently need ANOTHER copy to get it from the temp buffer into a C# type (which is done // inside the managedFunctionInvoker function) /*foreach (IntPtr paramProp in new NativeReflection.NativeFieldIterator(Runtime.Classes.UProperty, function, * EFieldIteratorType.Destructor, false)) * { * // When is this ever false? It seems to be checked in UObject::ProcessEvent() * // "Destroy local variables except function parameters." - used for BP locals? * Debug.Assert(Native_UProperty.IsInContainer(paramProp, paramsSize)); * * // Out params are copied to the memory maintained by the caller so only destroy "by value" parameters. * if (!Native_UProperty.HasAnyPropertyFlags(paramProp, EPropertyFlags.OutParm)) * { * Native_UProperty.DestroyValue_InContainer(paramProp, paramsBuffer); * } * }*/ }
/// <summary> /// Returns this property's propertyflags /// </summary> /// <returns></returns> public EPropertyFlags GetPropertyFlags() { return(Native_UProperty.GetPropertyFlags(Address)); }