Fix creating delegates to scripting methods after script reload

This commit is contained in:
2023-01-01 22:06:00 +02:00
parent 21c67b4777
commit 5b4e209816
2 changed files with 69 additions and 41 deletions

View File

@@ -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<MethodHolder>(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<InvokeThunkDelegate>(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<Type[], Type> MakeNewCustomDelegateFunc =
typeof(Expression).Assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers")
.GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static)
.CreateDelegate<Func<Type[], Type>>();
private static Func<Type[], Type> MakeNewCustomDelegateFunc;
private static Func<Type[], Type> 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<Func<Type[], Type>>();
// 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<Func<Type[], Type>>();
// 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);
}
}

View File

@@ -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
{
/// <summary>
/// Helper class for invoking managed methods from delegates.
/// </summary>
internal static class Invoker
{
internal delegate IntPtr MarshalAndInvokeDelegate(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr);
internal delegate IntPtr InvokeThunkDelegate(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs);
/// <summary>
/// Casts managed pointer to unmanaged pointer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static T* ToPointer<T>(IntPtr ptr) where T : unmanaged
{
return (T*)ptr.ToPointer();
}
internal static MethodInfo ToPointerMethod = typeof(Invoker).GetMethod(nameof(Invoker.ToPointer), BindingFlags.Static | BindingFlags.NonPublic);
/// <summary>
/// Creates a delegate for invoker to pass parameters as references.
/// </summary>
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<ParameterExpression> parameterExpressions = new List<ParameterExpression>(delegateParameters.Select(x => Expression.Parameter(x)));
List<Expression> callExpressions = new List<Expression>(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<TRet>(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<TInstance>
{
internal delegate void InvokerDelegate(object instance);