From 6010c13d406935863079bcc00754b5432734e687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Rozs=C3=ADval?= Date: Wed, 28 Jun 2023 12:24:46 +0200 Subject: [PATCH] [managed-static-registrar] Remove use of reflection in UCOs of generic types (#18421) Closes #18356 In the static UnmanagedCallersOnly methods we don't know the generic parameters of the type we're working with and we need to use this trick to be able to call methods on the generic type without using reflection. When we call a non-generic interface method implemented on a generic class, the .NET runtime will resolve the generic parameters for us. In the implementation of the interface method, we can simply use the generic parameters and generate the same code we usually generate in the UnmanagedCallersOnly callback method. This is an example of the code we generate in addition to user code: ```csharp internal interface __IRegistrarGenericTypeProxy__CustomNSObject_1__ { void __IRegistrarGenericTypeProxy__CustomNSObject_1____SomeMethod (IntPtr p0); } public class CustomNSObject : NSObject, __IRegistrarGenericTypeProxy__CustomNSObject_1__ where T : NSObject { [Export ("someMethod:")] public void SomeMethod (T someInput) { // ... } // generated implementation of the proxy interface: public void __IRegistrarGenericTypeProxy__CustomNSObject_1____SomeMethod (IntPtr sel, IntPtr p0, IntPtr* exception_gchandle) { try { var obj0 = (T) Runtime.GetNSObject (p0); SomeMethod (obj0); } catch (Exception ex) { *exception_gchandle = Runtime.AllocGCHandle (ex); } } // generated registrar callbacks: private static class __Registrar_Callbacks__ { [UnmanagedCallersOnly (EntryPoint = "_callback_1_CustomNSObject_1_SomeMethod")] public unsafe static void callback_1_CustomNSObject_1_SomeMethod (IntPtr pobj, IntPtr sel, IntPtr p0, IntPtr* exception_gchandle) { var proxy = (__IRegistrarGenericTypeProxy__CustomNSObject_1__)Runtime.GetNSObject (pobj); proxy.__IRegistrarGenericTypeProxy__CustomNSObject_1____SomeMethod (sel, p0, exception_gchandle); } } } ``` ```csharp // regular non-generic class for comparison: public class CustomNSObject : NSObject where T : NSObject { [Export ("someMethod:")] public void SomeMethod (NSSet someInput) { // ... } private static class __Registrar_Callbacks__ { [UnmanagedCallersOnly (EntryPoint = "_callback_1_CustomNSObject_1_SomeMethod")] public unsafe static void callback_1_CustomNSObject_1_SomeMethod (IntPtr pobj, IntPtr sel, IntPtr p0, IntPtr* exception_gchandle) { try { NSSet obj0 = Runtime.GetNSObject (p0); SomeMethod (obj0); } catch (Exception ex) { *exception_gchandle = Runtime.AllocGCHandle (ex); } } } } ``` --------- Co-authored-by: Alex Soto --- .../Steps/ManagedRegistrarStep.cs | 288 ++++++++++-------- 1 file changed, 161 insertions(+), 127 deletions(-) diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index 33ff8b81e2d..9f8811fbde4 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -148,10 +148,14 @@ protected override void TryProcessAssembly (AssemblyDefinition assembly) var current_trampoline_lists = new AssemblyTrampolineInfo (); Configuration.AssemblyTrampolineInfos [assembly] = current_trampoline_lists; + var proxyInterfaces = new List (); var modified = false; foreach (var type in assembly.MainModule.Types) - modified |= ProcessType (type, current_trampoline_lists); + modified |= ProcessType (type, current_trampoline_lists, proxyInterfaces); + + foreach (var additionalType in proxyInterfaces) + assembly.MainModule.Types.Add (additionalType); // Make sure the linker saves any changes in the assembly. if (modified) { @@ -162,12 +166,12 @@ protected override void TryProcessAssembly (AssemblyDefinition assembly) abr.ClearCurrentAssembly (); } - bool ProcessType (TypeDefinition type, AssemblyTrampolineInfo infos) + bool ProcessType (TypeDefinition type, AssemblyTrampolineInfo infos, List proxyInterfaces) { var modified = false; if (type.HasNestedTypes) { foreach (var nested in type.NestedTypes) - modified |= ProcessType (nested, infos); + modified |= ProcessType (nested, infos, proxyInterfaces); } // Figure out if there are any types we need to process @@ -199,7 +203,7 @@ bool ProcessType (TypeDefinition type, AssemblyTrampolineInfo infos) // Create an UnmanagedCallersOnly method for each method we need to wrap foreach (var method in methods_to_wrap) { try { - CreateUnmanagedCallersMethod (method, infos); + CreateUnmanagedCallersMethod (method, infos, proxyInterfaces); } catch (Exception e) { AddException (ErrorHelper.CreateError (99, e, "Failed to create an UnmanagedCallersOnly trampoline for {0}: {1}", method.FullName, e.Message)); } @@ -270,15 +274,10 @@ void Trace (ILProcessor il, string message) } int counter; - void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineInfo infos) + void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineInfo infos, List proxyInterfaces) { var baseMethod = StaticRegistrar.GetBaseMethodInTypeHierarchy (method); var placeholderType = abr.System_IntPtr; - ParameterDefinition? callSuperParameter = null; - VariableDefinition? returnVariable = null; - var leaveTryInstructions = new List (); - var isVoid = method.ReturnType.Is ("System", "Void"); - var name = $"callback_{counter++}_{Sanitize (method.DeclaringType.FullName)}_{Sanitize (method.Name)}"; var callbackType = method.DeclaringType.NestedTypes.SingleOrDefault (v => v.Name == "__Registrar_Callbacks__"); @@ -295,6 +294,116 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn // If the target method is marked, then we must mark the trampoline as well. method.CustomAttributes.Add (CreateDynamicDependencyAttribute (callbackType, callback.Name)); + callback.AddParameter ("pobj", abr.System_IntPtr); + + var isGeneric = method.DeclaringType.HasGenericParameters; + if (isGeneric && method.IsStatic) { + throw ErrorHelper.CreateError (4130 /* The registrar cannot export static methods in generic classes ('{0}'). */, method.FullName); + } else if (isGeneric && !method.IsConstructor) { + // We generate a proxy interface for each generic NSObject subclass. In the static UnmanagedCallersOnly methods we don't + // know the generic parameters of the type we're working with and we need to use this trick to be able to call methods on the + // generic type without using reflection. This is an example of the code we generate in addition to user code: + // + // + // internal interface __IRegistrarGenericTypeProxy__CustomNSObject_1__ + // { + // void __IRegistrarGenericTypeProxy__CustomNSObject_1____SomeMethod (IntPtr p0); + // } + // + // public class CustomNSObject : NSObject, __IRegistrarGenericTypeProxy__CustomNSObject_1__ + // where T : NSObject + // { + // [Export ("someMethod:")] + // public void SomeMethod (T someInput) + // { + // // ... + // } + // + // // generated implementation of the proxy interface: + // public void __IRegistrarGenericTypeProxy__CustomNSObject_1____SomeMethod (IntPtr sel, IntPtr p0, IntPtr* exception_gchandle) + // { + // try { + // var obj0 = Runtime.GetNSObject (p0); + // SomeMethod (obj0); + // } catch (Exception ex) { + // *exception_gchandle = Runtime.AllocGCHandle (ex); + // } + // } + // + // // generated registrar callbacks: + // private static class __Registrar_Callbacks__ + // { + // [UnmanagedCallersOnly (EntryPoint = "_callback_1_CustomNSObject_1_SomeMethod")] + // public unsafe static void callback_1_CustomNSObject_1_SomeMethod (IntPtr pobj, IntPtr sel, IntPtr p0, IntPtr* exception_gchandle) + // { + // var proxy = (__IRegistrarGenericTypeProxy__CustomNSObject_1__)Runtime.GetNSObject (pobj); + // proxy.__IRegistrarGenericTypeProxy__CustomNSObject_1____SomeMethod (sel, p0, exception_gchandle); + // } + // } + // } + + var proxyInterfaceName = $"__IRegistrarGenericTypeProxy__{Sanitize (method.DeclaringType.FullName)}__"; + TypeDefinition? proxyInterface = proxyInterfaces.SingleOrDefault (v => v.Name == proxyInterfaceName && v.Namespace == "ObjCRuntime"); + if (proxyInterface is null) { + proxyInterface = new TypeDefinition ("ObjCRuntime", proxyInterfaceName, TypeAttributes.NotPublic | TypeAttributes.Interface | TypeAttributes.Abstract); + method.DeclaringType.Interfaces.Add (new InterfaceImplementation (proxyInterface)); + proxyInterfaces.Add (proxyInterface); + } + + var methodName = $"{proxyInterfaceName}_{method.Name}"; + var interfaceMethod = proxyInterface.AddMethod (methodName, MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Abstract | MethodAttributes.Virtual, placeholderType); + var implementationMethod = method.DeclaringType.AddMethod (methodName, MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Final, placeholderType); + + // the callback will only call the proxy method and the proxy method will perform all the conversions + EmitCallToExportedMethod (method, implementationMethod); + + // now copy the return type and params (incl. sel and exception_gchandle) to the UCO itself + // and also to the proxy interface + callback.ReturnType = implementationMethod.ReturnType; + interfaceMethod.ReturnType = implementationMethod.ReturnType; + + foreach (var parameter in implementationMethod.Parameters) { + callback.AddParameter (parameter.Name, parameter.ParameterType); + interfaceMethod.AddParameter (parameter.Name, parameter.ParameterType); + } + + // we need to wait until we know all the parameters of the interface method before we generate this method + EmitCallToProxyMethod (method, callback, interfaceMethod); + } else { + EmitCallToExportedMethod (method, callback); + } + } + + public void EmitCallToProxyMethod (MethodDefinition method, MethodDefinition callback, MethodDefinition proxyInterfaceMethod) + { + _ = callback.CreateBody (out var il); + + // We don't know the generic parameters of the type we're working with but we know it is a NSObject and it + // implements the proxy interface. The generic parameters will be resolved in the proxy method through the v-table. + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Call, abr.Runtime_GetNSObject__System_IntPtr); + il.Emit (OpCodes.Castclass, proxyInterfaceMethod.DeclaringType); + + if (callback.HasParameters) { + // skip the first argument (the handle of the object) + foreach (var parameter in callback.Parameters.Skip (1)) { + il.Emit (OpCodes.Ldarg, parameter); + } + } + + il.Emit (OpCodes.Callvirt, proxyInterfaceMethod); + il.Emit (OpCodes.Ret); + } + + public void EmitCallToExportedMethod (MethodDefinition method, MethodDefinition callback) + { + var baseMethod = StaticRegistrar.GetBaseMethodInTypeHierarchy (method); + var placeholderType = abr.System_IntPtr; + ParameterDefinition? callSuperParameter = null; + VariableDefinition? returnVariable = null; + var leaveTryInstructions = new List (); + var isVoid = method.ReturnType.Is ("System", "Void"); + var body = callback.CreateBody (out var il); var placeholderInstruction = il.Create (OpCodes.Nop); var placeholderNextInstruction = il.Create (OpCodes.Nop); @@ -303,35 +412,12 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn var isCategory = categoryAttribute is not null; var isInstanceCategory = isCategory && StaticRegistrar.HasThisAttribute (method); var isGeneric = method.DeclaringType.HasGenericParameters; - var isDynamicInvoke = isGeneric; - VariableDefinition? selfVariable = null; Trace (il, $"ENTER"); - callback.AddParameter ("pobj", abr.System_IntPtr); - if (!isVoid || method.IsConstructor) returnVariable = body.AddVariable (placeholderType); - if (isGeneric) { - if (method.IsStatic) - throw ErrorHelper.CreateError (4130 /* The registrar cannot export static methods in generic classes ('{0}'). */, method.FullName); - - if (!method.IsConstructor) { - il.Emit (OpCodes.Ldtoken, method); - - il.Emit (OpCodes.Ldarg_0); - EmitConversion (method, il, method.DeclaringType, true, -1, out var nativeType, postProcessing, selfVariable, isDynamicInvoke: isDynamicInvoke); - - selfVariable = body.AddVariable (abr.System_Object); - il.Emit (OpCodes.Stloc, selfVariable); - il.Emit (OpCodes.Ldloc, selfVariable); - il.Emit (OpCodes.Ldtoken, method.DeclaringType); - il.Emit (OpCodes.Ldtoken, method); - il.Emit (OpCodes.Call, abr.Runtime_FindClosedMethod); - } - } - // Our code emission is intermingled with creating the method signature (the code to convert between native and managed values is also the best location // to determine exactly which are the corresponding native and managed types in the method signatures). Unfortunately we might need to skip code emission // after a certain point (if we detected a failure scenario where we're just throwing an exception, there's no need to keep generating code for that @@ -341,7 +427,7 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn Instruction? skipEverythingAfter = null; if (isInstanceCategory) { il.Emit (OpCodes.Ldarg_0); - EmitConversion (method, il, method.Parameters [0].ParameterType, true, 0, out var nativeType, postProcessing, selfVariable, isDynamicInvoke: isDynamicInvoke); + EmitConversion (method, il, method.Parameters [0].ParameterType, true, 0, out var nativeType, postProcessing); } else if (method.IsStatic) { // nothing to do } else if (method.IsConstructor) { @@ -373,7 +459,6 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn // We're throwing an exception, so there's no need for any more code. skipEverythingAfter = il.Body.Instructions.Last (); } else { - il.Emit (OpCodes.Ldarg_0); postLeaveBranch.Operand = il.Body.Instructions.Last (); var git = new GenericInstanceMethod (abr.NSObject_AllocateNSObject); @@ -381,10 +466,13 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn il.Emit (OpCodes.Call, git); il.Emit (OpCodes.Dup); // this is for the call to ObjCRuntime.NativeObjectExtensions::GetHandle after the call to the constructor } + } else if (isGeneric) { + // this is a proxy method and we can simply use `this` without any conversion + il.Emit (OpCodes.Ldarg_0); } else { // instance method il.Emit (OpCodes.Ldarg_0); - EmitConversion (method, il, method.DeclaringType, true, -1, out var nativeType, postProcessing, selfVariable, isDynamicInvoke: isDynamicInvoke); + EmitConversion (method, il, method.DeclaringType, true, -1, out var nativeType, postProcessing); } callback.AddParameter ("sel", abr.System_IntPtr); @@ -395,11 +483,6 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn if (method.HasParameters) managedParameterCount = method.Parameters.Count; - if (isGeneric) { - il.Emit (OpCodes.Ldc_I4, managedParameterCount); - il.Emit (OpCodes.Newarr, abr.System_Object); - } - if (method.HasParameters) { for (var p = parameterStart; p < managedParameterCount; p++) { var nativeParameter = callback.AddParameter ($"p{p}", placeholderType); @@ -407,28 +490,17 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn var managedParameterType = method.Parameters [p].ParameterType; var baseParameter = baseMethod.Parameters [p]; var isOutParameter = IsOutParameter (method, p, baseParameter); - if (isDynamicInvoke && !isOutParameter) { - if (parameterStart != 0) { - AddException (ErrorHelper.CreateError (99, $"Unexpected parameterStart {parameterStart} in method {GetMethodSignature (method)} for parameter {p}")); - continue; - } - il.Emit (OpCodes.Dup); - il.Emit (OpCodes.Ldc_I4, p); - } + if (!isOutParameter) { il.EmitLoadArgument (nativeParameterIndex); } - if (EmitConversion (method, il, managedParameterType, true, p, out var nativeType, postProcessing, selfVariable, isOutParameter, nativeParameterIndex, isDynamicInvoke)) { + + if (EmitConversion (method, il, managedParameterType, true, p, out var nativeType, postProcessing, isOutParameter, nativeParameterIndex)) { nativeParameter.ParameterType = nativeType; } else { nativeParameter.ParameterType = placeholderType; AddException (ErrorHelper.CreateError (99, "Unable to emit conversion for parameter {2} of type {0}. Method: {1}", method.Parameters [p].ParameterType, GetMethodSignatureWithSourceCode (method), p)); } - if (isDynamicInvoke && !isOutParameter) { - if (managedParameterType.IsValueType) - il.Emit (OpCodes.Box, managedParameterType); - il.Emit (OpCodes.Stelem_Ref); - } } } @@ -437,16 +509,9 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn callback.AddParameter ("exception_gchandle", new PointerType (abr.System_IntPtr)); - var isDynamicInvokeReturnType = false; - if (isGeneric) { - il.Emit (OpCodes.Call, abr.MethodBase_Invoke); - if (isVoid) { - il.Emit (OpCodes.Pop); - } else if (method.ReturnType.IsValueType) { - il.Emit (OpCodes.Unbox_Any, method.ReturnType); - } else { - isDynamicInvokeReturnType = true; - } + if (isGeneric && !method.IsConstructor) { + var targetMethod = method.DeclaringType.CreateMethodReferenceOnGenericType (method, method.DeclaringType.GenericParameters.ToArray ()); + il.Emit (OpCodes.Call, targetMethod); } else if (method.IsStatic) { il.Emit (OpCodes.Call, method); } else { @@ -454,7 +519,7 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn } if (returnVariable is not null) { - if (EmitConversion (method, il, method.ReturnType, false, -1, out var nativeReturnType, postProcessing, selfVariable, isDynamicInvoke: isDynamicInvoke, isDynamicInvokeReturnType: isDynamicInvokeReturnType)) { + if (EmitConversion (method, il, method.ReturnType, false, -1, out var nativeReturnType, postProcessing)) { returnVariable.VariableType = nativeReturnType; callback.ReturnType = nativeReturnType; } else { @@ -478,7 +543,7 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn body.Instructions.RemoveAt (i); } - AddExceptionHandler (il, returnVariable, placeholderNextInstruction, out var eh, out var leaveEHInstruction); + AddExceptionHandler (il, returnVariable, placeholderNextInstruction, isGeneric && !method.IsConstructor, out var eh, out var leaveEHInstruction); // Generate code to return null/default value/void if (returnVariable is not null) { @@ -516,7 +581,7 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn eh.HandlerEnd = (Instruction) leaveEHInstruction.Operand; } - void AddExceptionHandler (ILProcessor il, VariableDefinition? returnVariable, Instruction placeholderNextInstruction, out ExceptionHandler eh, out Instruction leaveEHInstruction) + void AddExceptionHandler (ILProcessor il, VariableDefinition? returnVariable, Instruction placeholderNextInstruction, bool isGeneric, out ExceptionHandler eh, out Instruction leaveEHInstruction) { var body = il.Body; var method = body.Method; @@ -531,7 +596,7 @@ void AddExceptionHandler (ILProcessor il, VariableDefinition? returnVariable, In il.Emit (OpCodes.Stloc, exceptionVariable); eh.HandlerStart = il.Body.Instructions.Last (); eh.TryEnd = eh.HandlerStart; - il.Emit (OpCodes.Ldarg, method.Parameters.Count - 1); + il.Emit (OpCodes.Ldarg, isGeneric ? method.Parameters.Count : method.Parameters.Count - 1); il.Emit (OpCodes.Ldloc, exceptionVariable); il.Emit (OpCodes.Call, abr.Runtime_AllocGCHandle); il.Emit (OpCodes.Stind_I); @@ -583,7 +648,7 @@ bool IsNSObject (TypeReference type) // This emits a conversion between the native and the managed representation of a parameter or return value, // and returns the corresponding native type. The returned nativeType will (must) be a blittable type. - bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type, bool toManaged, int parameter, [NotNullWhen (true)] out TypeReference? nativeType, List postProcessing, VariableDefinition? selfVariable, bool isOutParameter = false, int nativeParameterIndex = -1, bool isDynamicInvoke = false, bool isDynamicInvokeReturnType = false) + bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type, bool toManaged, int parameter, [NotNullWhen (true)] out TypeReference? nativeType, List postProcessing, bool isOutParameter = false, int nativeParameterIndex = -1) { nativeType = null; @@ -594,8 +659,6 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type GenerateConversionToManaged (method, il, bindAsAttribute.OriginalType, type, "descriptiveMethodName", parameter, out nativeType); return true; } else { - if (isDynamicInvokeReturnType) - il.Emit (OpCodes.Castclass, type); GenerateConversionToNative (method, il, type, bindAsAttribute.OriginalType, "descriptiveMethodName", out nativeType); return true; } @@ -629,9 +692,6 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type return true; } - if (isDynamicInvokeReturnType) - AddException (ErrorHelper.CreateError (99, "Unexpected value type {0}: can't result from dynamic invoke. Method: {1}", type, GetMethodSignatureWithSourceCode (method))); - // no conversion necessary if we're any other value type nativeType = type; return true; @@ -641,8 +701,6 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type var elementType = pt.ElementType; if (!elementType.IsValueType) AddException (ErrorHelper.CreateError (99, "Unexpected pointer type {0}: must be a value type. Method: {1}", type, GetMethodSignatureWithSourceCode (method))); - if (isDynamicInvokeReturnType) - AddException (ErrorHelper.CreateError (99, "Unexpected pointer type {0}: can't result from dynamic invoke. Method: {1}", type, GetMethodSignatureWithSourceCode (method))); // no conversion necessary either way nativeType = type; return true; @@ -707,9 +765,10 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type EnsureVisible (method, managed_to_native); EnsureVisible (method, native_to_managed); - var indirectVariable = il.Body.AddVariable (elementType); + // brt.ElementType might be a generic type, so it should be use here it instead of elementType + var indirectVariable = il.Body.AddVariable (brt.ElementType); // We store a copy of the value in a separate variable, to detect if it changes. - var copyIndirectVariable = il.Body.AddVariable (elementType); + var copyIndirectVariable = il.Body.AddVariable (brt.ElementType); // We don't read the input for 'out' parameters, it might be garbage. if (!isOutParameter) { @@ -718,15 +777,18 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type if (addBeforeNativeToManagedCall is not null) il.Append (addBeforeNativeToManagedCall); il.Emit (OpCodes.Call, native_to_managed); - if (isDynamicInvoke) { - il.Emit (OpCodes.Ldloc, indirectVariable); - } else { - il.Emit (OpCodes.Ldloca, indirectVariable); - } - } else { - if (!isDynamicInvoke) - il.Emit (OpCodes.Ldloca, indirectVariable); } + + if (IsOpenType (brt.ElementType)) { + // for generic types try to verify that the variable is of the correct type + // by casting it + il.Emit (OpCodes.Ldloc, indirectVariable); + il.Emit (OpCodes.Unbox_Any, brt.ElementType); + il.Emit (OpCodes.Stloc, indirectVariable); + } + + il.Emit (OpCodes.Ldloca, indirectVariable); + postProcessing.Add (il.CreateLoadArgument (nativeParameterIndex)); postProcessing.Add (il.Create (OpCodes.Ldloc, indirectVariable)); postProcessing.Add (il.Create (OpCodes.Ldloc, copyIndirectVariable)); @@ -748,39 +810,32 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type if (type is ArrayType at) { var elementType = at.GetElementType (); if (elementType.Is ("System", "String")) { - if (!toManaged && isDynamicInvokeReturnType) - il.Emit (OpCodes.Castclass, new ArrayType (abr.System_String)); il.Emit (OpCodes.Call, toManaged ? abr.CFArray_StringArrayFromHandle : abr.RegistrarHelper_CreateCFArray); nativeType = abr.ObjCRuntime_NativeHandle; return true; } - var isGenericParameter = false; - if (elementType is GenericParameter gp) { + GenericParameter? gp = elementType as GenericParameter; + if (gp is not null) { if (!StaticRegistrar.VerifyIsConstrainedToNSObject (gp, out var constrained)) { AddException (ErrorHelper.CreateError (99, "Incorrectly constrained generic parameter. Method: {0}", GetMethodSignatureWithSourceCode (method))); return false; } elementType = constrained; - isGenericParameter = true; } var isNSObject = elementType.IsNSObject (DerivedLinkContext); var isNativeObject = StaticRegistrar.IsNativeObject (elementType); if (isNSObject || isNativeObject) { if (toManaged) { - if (isGenericParameter) { - il.Emit (OpCodes.Ldloc, selfVariable); - il.Emit (OpCodes.Ldtoken, method.DeclaringType); - il.Emit (OpCodes.Ldtoken, method); - il.Emit (OpCodes.Ldc_I4, parameter); - il.Emit (OpCodes.Call, abr.Runtime_FindClosedParameterType); - il.Emit (OpCodes.Call, abr.NSArray_ArrayFromHandle); + var gim = new GenericInstanceMethod (abr.NSArray_ArrayFromHandle_1); + if (gp is not null) { + var gemericParameter = method.DeclaringType.GenericParameters.Single (x => x.Name == gp.Name); + gim.GenericArguments.Add (gemericParameter); } else { - var gim = new GenericInstanceMethod (abr.NSArray_ArrayFromHandle_1); gim.GenericArguments.Add (elementType); - il.Emit (OpCodes.Call, gim); } + il.Emit (OpCodes.Call, gim); } else { var retain = StaticRegistrar.HasReleaseAttribute (method); il.Emit (retain ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); @@ -801,11 +856,8 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type il.Emit (OpCodes.Call, abr.Runtime_CopyAndAutorelease); if (IsOpenType (type)) { il.Emit (OpCodes.Call, abr.Runtime_GetNSObject__System_IntPtr); - if (!isDynamicInvoke) - AddException (ErrorHelper.CreateError (99, "Unable to call a statically resolved method 1 with object in {0} - {1} - {2}", GetMethodSignature (method), il.Body.Method.Name, type.FullName)); - - // We're calling the target method dynamically (using MethodBase.Invoke), so there's no - // need to check the type of the returned object, because MethodBase.Invoke will do type checks. + // cast to the generic type to verify that the item is actually of the correct type + il.Emit (OpCodes.Unbox_Any, type); } else { il.Emit (OpCodes.Ldarg_1); // SEL il.Emit (OpCodes.Ldtoken, method); @@ -815,11 +867,9 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type il.Emit (OpCodes.Stloc, tmpVariable); il.Emit (OpCodes.Ldloc, tmpVariable); } + nativeType = abr.System_IntPtr; } else { - if (isDynamicInvokeReturnType) - il.Emit (OpCodes.Castclass, abr.Foundation_NSObject); - if (parameter == -1) { var retain = StaticRegistrar.HasReleaseAttribute (method); il.Emit (OpCodes.Dup); @@ -840,11 +890,8 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type if (toManaged) { if (IsOpenType (type)) { il.Emit (OpCodes.Call, abr.Runtime_GetNSObject__System_IntPtr); - if (!isDynamicInvoke) - AddException (ErrorHelper.CreateError (99, "Unable to call a statically resolved method 2 with object in {0} - {1} - {2}", GetMethodSignature (method), il.Body.Method.Name, type.FullName)); - - // We're calling the target method dynamically (using MethodBase.Invoke), so there's no - // need to check the type of the returned object, because MethodBase.Invoke will do type checks. + // cast to the generic type to verify that the item is actually of the correct type + il.Emit (OpCodes.Unbox_Any, type); } else { var nativeObjType = StaticRegistrar.GetInstantiableType (type.Resolve (), exceptions, GetMethodSignature (method)); il.Emit (OpCodes.Ldc_I4_0); // false @@ -860,19 +907,12 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type if (parameter == -1) { var retain = StaticRegistrar.HasReleaseAttribute (method); var isNSObject = IsNSObject (type); - - if (isDynamicInvokeReturnType) - il.Emit (OpCodes.Castclass, isNSObject ? abr.Foundation_NSObject : abr.ObjCRuntime_INativeObject); - if (retain) { il.Emit (OpCodes.Call, isNSObject ? abr.Runtime_RetainNSObject : abr.Runtime_RetainNativeObject); } else { il.Emit (OpCodes.Call, isNSObject ? abr.Runtime_RetainAndAutoreleaseNSObject : abr.Runtime_RetainAndAutoreleaseNativeObject); } } else { - if (isDynamicInvokeReturnType) - il.Emit (OpCodes.Castclass, abr.ObjCRuntime_INativeObject); - il.Emit (OpCodes.Call, abr.NativeObjectExtensions_GetHandle); } nativeType = abr.ObjCRuntime_NativeHandle; @@ -881,9 +921,6 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type } if (type.Is ("System", "String")) { - if (!toManaged && isDynamicInvokeReturnType) - il.Emit (OpCodes.Castclass, abr.System_String); - il.Emit (OpCodes.Call, toManaged ? abr.CFString_FromHandle : abr.CFString_CreateNative); nativeType = abr.ObjCRuntime_NativeHandle; return true; @@ -942,9 +979,6 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type } } - if (isDynamicInvokeReturnType) - il.Emit (OpCodes.Castclass, abr.System_Delegate); - // the delegate is already on the stack if (createBlockMethod is not null) { EnsureVisible (method, createBlockMethod);