Fix creating delegates to scripting methods after script reload
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user