diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 2be91651e..a9df6c96c 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -1067,8 +1067,7 @@ namespace FlaxEngine var isCollectible = false; #endif scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible); - - DelegateHelpers.Init(); + DelegateHelpers.InitMethods(); } [UnmanagedCallersOnly] @@ -2323,17 +2322,16 @@ namespace FlaxEngine } } + private delegate IntPtr InvokeThunkDelegate(ManagedHandle instanceHandle, IntPtr param1, IntPtr param2, IntPtr param3, IntPtr param4, IntPtr param5, IntPtr param6, IntPtr param7); + [UnmanagedCallersOnly] internal static IntPtr GetMethodUnmanagedFunctionPointer(ManagedHandle methodHandle) { MethodHolder methodHolder = Unsafe.As(methodHandle.Target); // Wrap the method call, this is needed to get the object instance from ManagedHandle and to pass the exception back to native side - MethodInfo invokeThunk = typeof(ThunkContext).GetMethod(nameof(ThunkContext.InvokeThunk)); - Type delegateType = DelegateHelpers.MakeNewCustomDelegate(invokeThunk.GetParameters().Select(x => x.ParameterType).Append(invokeThunk.ReturnType).ToArray()); - ThunkContext context = new ThunkContext(methodHolder.method); - Delegate methodDelegate = invokeThunk.CreateDelegate(delegateType, context); + Delegate methodDelegate = typeof(ThunkContext).GetMethod(nameof(ThunkContext.InvokeThunk)).CreateDelegate(context); IntPtr functionPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate); // Keep a reference to the delegate to prevent it from being garbage collected @@ -2491,7 +2489,8 @@ namespace FlaxEngine System.Threading.Thread.Sleep(1); // TODO: benchmark collectible setting performance, maybe enable it only in editor builds? - scriptingAssemblyLoadContext = new AssemblyLoadContext(null, true); + scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", true); + DelegateHelpers.InitMethods(); } [UnmanagedCallersOnly] @@ -2791,28 +2790,36 @@ namespace FlaxEngine private static class DelegateHelpers { - private static readonly Func MakeNewCustomDelegateFunc = - typeof(Expression).Assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers") - .GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static) - .CreateDelegate>(); + private static Func MakeNewCustomDelegateFunc; + private static Func MakeNewCustomDelegateFuncCollectible; - internal static void Init() + internal static void InitMethods() { - // Ensures the MakeNewCustomDelegate is put in the collectible ALC? - using var ctx = scriptingAssemblyLoadContext.EnterContextualReflection(); + MakeNewCustomDelegateFunc = + typeof(Expression).Assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers") + .GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate>(); + // Load System.Linq.Expressions assembly to collectible ALC. + // The dynamic assembly where delegates are stored is cached in the DelegateHelpers class, so we should + // use the DelegateHelpers in collectible ALC to make sure the delegates are also stored in the same ALC. + Assembly assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(typeof(Expression).Assembly.Location); + MakeNewCustomDelegateFuncCollectible = + assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers") + .GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate>(); + + // Create dummy delegates to force the dynamic Snippets assembly to be loaded in correcet ALCs MakeNewCustomDelegateFunc(new[] { typeof(void) }); + { + // Ensure the new delegate is placed in the collectible ALC + using var ctx = scriptingAssemblyLoadContext.EnterContextualReflection(); + MakeNewCustomDelegateFuncCollectible(new[] { typeof(void) }); + } } internal static Type MakeNewCustomDelegate(Type[] parameters) { - if (parameters.Any(x => scriptingAssemblyLoadContext.Assemblies.Contains(x.Module.Assembly))) - { - // Ensure the new delegate is placed in the collectible ALC - //using var ctx = scriptingAssemblyLoadContext.EnterContextualReflection(); - return MakeNewCustomDelegateFunc(parameters); - } - + if (parameters.Any(x => x.IsCollectible)) + return MakeNewCustomDelegateFuncCollectible(parameters); return MakeNewCustomDelegateFunc(parameters); } } diff --git a/Source/Engine/Engine/NativeInterop_Invoker.cs b/Source/Engine/Engine/NativeInterop_Invoker.cs index 21f9770e9..2098c653c 100644 --- a/Source/Engine/Engine/NativeInterop_Invoker.cs +++ b/Source/Engine/Engine/NativeInterop_Invoker.cs @@ -1,7 +1,5 @@ #if USE_NETCORE - using System; -using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -13,40 +11,64 @@ namespace FlaxEngine { internal unsafe static partial class NativeInterop { + /// + /// Helper class for invoking managed methods from delegates. + /// internal static class Invoker { internal delegate IntPtr MarshalAndInvokeDelegate(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr); internal delegate IntPtr InvokeThunkDelegate(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs); + /// + /// Casts managed pointer to unmanaged pointer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static T* ToPointer(IntPtr ptr) where T : unmanaged { return (T*)ptr.ToPointer(); } + internal static MethodInfo ToPointerMethod = typeof(Invoker).GetMethod(nameof(Invoker.ToPointer), BindingFlags.Static | BindingFlags.NonPublic); /// /// Creates a delegate for invoker to pass parameters as references. /// - internal static Delegate CreateDelegateFromMethod(MethodInfo method, bool byRefParameters = true) + internal static Delegate CreateDelegateFromMethod(MethodInfo method, bool passParametersByRef = true) { - Type[] methodParameters = method.GetParameters().Select(x => x.ParameterType).ToArray(); - Type[] delegateParameters = method.GetParameters().Select(x => x.ParameterType.IsPointer ? typeof(IntPtr) : x.ParameterType).ToArray(); - if (byRefParameters) - delegateParameters = delegateParameters.Select(x => x.IsByRef ? x : x.MakeByRefType()).ToArray(); + Type[] methodParameters; + if (method.IsStatic) + methodParameters = method.GetParameters().Select(x => x.ParameterType).ToArray(); + else + methodParameters = method.GetParameters().Select(x => x.ParameterType).Prepend(method.DeclaringType).ToArray(); - List parameterExpressions = new List(delegateParameters.Select(x => Expression.Parameter(x))); - List callExpressions = new List(parameterExpressions); - for (int i = 0; i < callExpressions.Count; i++) - if (methodParameters[i].IsPointer) - callExpressions[i] = Expression.Call(null, typeof(Invoker).GetMethod(nameof(Invoker.ToPointer), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(methodParameters[i].GetElementType()), parameterExpressions[i]); + // Pass delegate parameters by reference + Type[] delegateParameters = methodParameters.Select(x => x.IsPointer ? typeof(IntPtr) : x) + .Select(x => passParametersByRef && !x.IsByRef ? x.MakeByRefType() : x).ToArray(); + if (!method.IsStatic && passParametersByRef) + delegateParameters[0] = method.DeclaringType; - var firstParamExp = Expression.Parameter(method.DeclaringType); - var callDelegExp = Expression.Call(!method.IsStatic ? firstParamExp : null, method, callExpressions.ToArray()); + // Convert unmanaged pointer parameters to IntPtr + ParameterExpression[] parameterExpressions = delegateParameters.Select(x => Expression.Parameter(x)).ToArray(); + Expression[] callExpressions = new Expression[methodParameters.Length]; + for (int i = 0; i < methodParameters.Length; i++) + { + Type parameterType = methodParameters[i]; + if (parameterType.IsPointer) + { + callExpressions[i] = + Expression.Call(null, ToPointerMethod.MakeGenericMethod(parameterType.GetElementType()), parameterExpressions[i]); + } + else + callExpressions[i] = parameterExpressions[i]; + } - if (!method.IsStatic) - parameterExpressions.Insert(0, firstParamExp); - var lambda = Expression.Lambda(callDelegExp, parameterExpressions.ToArray()); - - return lambda.Compile(); + // Create and compile the delegate + MethodCallExpression callDelegExp; + if (method.IsStatic) + callDelegExp = Expression.Call(null, method, callExpressions.ToArray()); + else + callDelegExp = Expression.Call(parameterExpressions[0], method, callExpressions.Skip(1).ToArray()); + Type delegateType = DelegateHelpers.MakeNewCustomDelegate(delegateParameters.Append(method.ReturnType).ToArray()); + return Expression.Lambda(delegateType, callDelegExp, parameterExpressions.ToArray()).Compile(); } internal static IntPtr MarshalReturnValue(ref TRet returnValue) @@ -75,7 +97,6 @@ namespace FlaxEngine return returnValue != null ? ManagedHandle.ToIntPtr(ManagedHandle.Alloc(returnValue, GCHandleType.Weak)) : IntPtr.Zero; } - internal static class InvokerNoRet0 { internal delegate void InvokerDelegate(object instance);