8000 [CoreCLR] Dynamically build custom delegate types to support [Export]… · dotnet/android@0a91e1e · GitHub
[go: up one dir, main page]

Skip to content

Commit 0a91e1e

Browse files
[CoreCLR] Dynamically build custom delegate types to support [Export] (#10503)
The problem is that the Marshal.GetFunctionPointerForDelegate method does not work with delegates with generic types, such as `Action<...>` or `Func<...>` (context: dotnet/runtime#32963), which was the case of all the delegates created in `Mono.Android.Export` through `DynamicMethod`. This PR replaces the use of these generic delegate types with dynamically built non-generic types. It is based on an existing implementation in java-interop (see [here][0]). The CoreCLR is able to marshal the delegates built with these types to function pointers just fine. In a previous attempt at fixing this issue (#10493), I chose a solution which modified the shape of `JniNativeMethodRegistration` which would be an ABI breaking change (dotnet/java-interop#1364). This implementation is much simpler and doesn't cause the same problem. Thanks to @jonpryor for steering me in this direction. [0]: https://github.com/dotnet/java-interop/blob/02bceb03f7c07858590d930ef507745a88200a48/src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs#L274-L311
1 parent 3b6542e commit 0a91e1e

File tree

2 files changed

+74
-71
lines changed

2 files changed

+74
-71
lines changed

src/Mono.Android.Export/CallbackCode.cs

Lines changed: 68 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -560,62 +560,82 @@ public DynamicCallbackCodeGenerator (MethodInfo method)
560560
lpgen.Add (DynamicInvokeTypeInfo.Get (p.ParameterType, GetExportKind (p)));
561561
parameter_type_infos = lpgen;
562562
}
563-
564-
static Type GetActionFuncType (int count, bool func)
565-
{
566-
if (func) {
567-
switch (count) {
568-
case 1: return typeof (Func<>);
569-
case 2: return typeof (Func<,>);
570-
case 3: return typeof (Func<,,>);
571-
case 4: return typeof (Func<,,,>);
572-
case 5: return typeof (Func<,,,,>);
573-
case 6: return typeof (Func<,,,,,>);
574-
case 7: return typeof (Func<,,,,,,>);
575-
case 8: return typeof (Func<,,,,,,,>);
576-
case 9: return typeof (Func<,,,,,,,,>);
577-
case 10: return typeof (Func<,,,,,,,,,>);
578-
case 11: return typeof (Func<,,,,,,,,,,>);
579-
case 12: return typeof (Func<,,,,,,,,,,,>);
580-
case 13: return typeof (Func<,,,,,,,,,,,,>);
581-
default: throw new NotSupportedException ();
582-
}
583-
} else {
584-
switch (count) {
585-
case 1: return typeof (Action<>);
586-
case 2: return typeof (Action<,>);
587-
case 3: return typeof (Action<,,>);
588-
case 4: return typeof (Action<,,,>);
589-
case 5: return typeof (Action<,,,,>);
590-
case 6: return typeof (Action<,,,,,>);
591-
case 7: return typeof (Action<,,,,,,>);
592-
case 8: return typeof (Action<,,,,,,,>);
593-
case 9: return typeof (Action<,,,,,,,,>);
594-
case 10: return typeof (Action<,,,,,,,,,>);
595-
case 11: return typeof (Action<,,,,,,,,,,>);
596-
case 12: return typeof (Action<,,,,,,,,,,,>);
597-
case 13: return typeof (Action<,,,,,,,,,,,,>);
598-
default: throw new NotSupportedException ();
599-
}
600-
}
601-
}
602563

603564
Type delegate_type;
565+
static readonly Type[] DelegateCtorSignature = [typeof (object), typeof (IntPtr)];
566+
604567
public override Type GetDelegateType ()
605568
{
606569
if (delegate_type == null) {
607-
var parms = new List<Type> ();
608-
parms.Add (typeof (IntPtr));
609-
parms.Add (typeof (IntPtr));
610-
parms.AddRange (parameter_type_infos.ConvertAll<Type> (p => p.NativeType));
611-
if (method.ReturnType == typeof (void))
612-
delegate_type = parms.Count == 0 ? typeof (Action) : GetActionFuncType (parms.Count, false).MakeGenericType (parms.ToArray ());
613-
else {
614-
parms.Add (return_type_info.NativeType);
615-
delegate_type = GetActionFuncType (parms.Count, true).MakeGenericType (parms.ToArray ());
570+
string delegateTypeName = $"callback_delegate_{EncodeMethodSignature (parameter_type_infos, return_type_info)}";
571+
lock (DynamicCallbackFactory.Module) {
572+
delegate_type = DynamicCallbackFactory.Module.GetType (delegateTypeName)
573+
?? CreateDelegateType (delegateTypeName);
616574
}
617575
}
576+
618577
return delegate_type;
578+
579+
// Based on https://github.com/dotnet/java-interop/blob/02bceb03f7c07858590d930ef507745a88200a48/src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs#L274-L311
580+
Type CreateDelegateType (string delegateTypeName)
581+
{
582+
const TypeAttributes TypeAttributes = TypeAttribute E7EE s.Class | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.AnsiClass | TypeAttributes.AutoClass;
583+
const MethodAttributes CtorAttributes = MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public;
584+
const MethodImplAttributes ImplAttributes = MethodImplAttributes.Runtime | MethodImplAttributes.Managed;
585+
const MethodAttributes InvokeAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual;
586+
587+
TypeBuilder typeBuilder = DynamicCallbackFactory.Module.DefineType (
588+
delegateTypeName, TypeAttributes, typeof (MulticastDelegate));
589+
590+
Type[] parms = new Type [parameter_type_infos.Count + 2];
591+
parms [0] = typeof (IntPtr);
592+
parms [1] = typeof (IntPtr);
593+
for (int i = 0; i < parameter_type_infos.Count; i++) {
594+
parms [i + 2] = parameter_type_infos [i].NativeType;
595+
}
596+
597+
typeBuilder.DefineConstructor (CtorAttributes, CallingConventions.Standard, DelegateCtorSignature)
598+
.SetImplementationFlags (ImplAttributes);
599+
typeBuilder.DefineMethod ("Invoke", InvokeAttributes, return_type_info.NativeType, parms)
600+
.SetImplementationFlags (ImplAttributes);
601+
602+
return typeBuilder.CreateTypeInfo ();
603+
}
604+
605+
// This "signature" is inspired by JNI signatures, but the only purpose it has is to create unique keys for
606+
// different delegate types. It is not a valid JNI signature.
607+
static string EncodeMethodSignature (List<DynamicInvokeTypeInfo> parameter_types, DynamicInvokeTypeInfo return_type)
608+
{
609+
return string.Create (
610+
length: parameter_types.Count + 2,
611+
state: (parameter_types, return_type),
612+
static (span, state) => {
613+
int pos = 0;
614+
foreach (var p in state.parameter_types) {
615+
span [pos++] = EncodeType (p.NativeType);
616+
}
617+
span [pos++] = '_';
618+
span [pos++] = EncodeType (state.return_type.NativeType);
619+
});
620+
621+
static char EncodeType (Type type)
622+
{
623+
if (type == typeof (bool)) return 'Z';
624+
if (type == typeof (byte)) return 'B';
625+
if (type == typeof (sbyte)) return 'B';
626+
if (type == typeof (char)) return 'C';
627+
if (type == typeof (short)) return 'S';
628+
if (type == typeof (ushort)) return 's';
629+
if (type == typeof (int)) return 'I';
630+
if (type == typeof (uint)) return 'i';
631+
if (type == typeof (long)) return 'J';
632+
if (type == typeof (ulong)) return 'j';
633+
if (type == typeof (float)) return 'F';
634+
if (type == typeof (double)) return 'D';
635+
if (type == typeof (void)) return 'V';
636+
return 'L';
637+
}
638+
}
619639
}
620640

621641
static int gen_count;

tests/MSBuildDeviceIntegration/Tests/MonoAndroidExportTest.cs

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,19 @@
88
using Mono.Debugging.Soft;
99
using NUnit.Framework;
1010
using Xamarin.ProjectTools;
11+
using Xamarin.Android.Tasks;
1112

1213
namespace Xamarin.Android.Build.Tests
1314
{
1415
[TestFixture]
1516
[Category ("UsesDevice")]
1617
public class MonoAndroidExportTest : DeviceTest
1718
{
18-
#pragma warning disable 414
19-
static object [] MonoAndroidExportTestCases = new object [] {
20-
new object[] {
21-
/* embedAssemblies */ true,
22-
/* isRelease */ false,
23-
},
24-
new object[] {
25-
/* embedAssemblies */ false,
26-
/* isRelease */ false,
27-
},
28-
new object[] {
29-
/* embedAssemblies */ true,
30-
/* isRelease */ false,
31-
},
32-
new object[] {
33-
/* embedAssemblies */ true,
34-
/* isRelease */ true,
35-
},
36-
};
37-
#pragma warning restore 414
38-
3919
[Test]
40-
[TestCaseSource (nameof (MonoAndroidExportTestCases))]
41-
public void MonoAndroidExportReferencedAppStarts (bool embedAssemblies, bool isRelease)
20+
public void MonoAndroidExportReferencedAppStarts (
21+
[Values (true, false)] bool embedAssemblies,
22+
[Values (true, false)] bool isRelease,
23+
[Values (AndroidRuntime.CoreCLR, AndroidRuntime.MonoVM)] AndroidRuntime runtime)
4224
{
4325
AssertCommercialBuild ();
4426
var proj = new XamarinAndroidApplicationProject () {
@@ -47,6 +29,7 @@ public void MonoAndroidExportReferencedAppStarts (bool embedAssemblies, bool isR
4729
new BuildItem.Reference ("Mono.Android.Export"),
4830
},
4931
};
32+
proj.SetRuntime (runtime);
5033
proj.Sources.Add (new BuildItem.Source ("ContainsExportedMethods.cs") {
5134
TextContent = () => @"using System;
5235
using Java.Interop;

0 commit comments

Comments
 (0)
0