From 8b5fc655e90dfa581a8d1ab30dea95e34084bd5b Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 12 Mar 2026 09:19:12 +0100 Subject: [PATCH] [TrimmableTypeMap] Add GetFunctionPointer(int) ordinal dispatch Emit GetFunctionPointer(int methodIndex) on ACW proxy types that maps ordinal indices to UnmanagedCallersOnly wrapper function pointers. The method emits a chain of comparisons: if (methodIndex == 0) return (IntPtr)&wrapper_0; if (methodIndex == 1) return (IntPtr)&wrapper_1; ... return IntPtr.Zero; This provides an alternative dispatch mechanism to RegisterNatives, allowing the runtime to look up function pointers by ordinal index. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Generator/TypeMapAssemblyEmitter.cs | 49 ++++++++++++++++++- .../TypeMapAssemblyGeneratorTests.cs | 18 +++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 775f48af607..ebf93916d4b 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -361,9 +361,10 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary + /// Emits GetFunctionPointer(int methodIndex) → IntPtr that maps ordinals to UCO wrapper function pointers. + /// Used as an alternative dispatch mechanism to RegisterNatives. + /// + void EmitGetFunctionPointer (List registrations, + Dictionary wrapperHandles) + { + // Collect resolved handles in registration order + var resolvedHandles = new List (); + foreach (var reg in registrations) { + if (wrapperHandles.TryGetValue (reg.WrapperMethodName, out var handle)) { + resolvedHandles.Add (handle); + } + } + + _pe.EmitBody ("GetFunctionPointer", + MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | + MethodAttributes.NewSlot | MethodAttributes.Final, + sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Int32 ()), + encoder => { + // For each registration, emit: + // ldarg.1 // load methodIndex + // ldc.i4 // load constant + // bne.un.s // if not equal, skip to next + // ldftn // load function pointer + // ret + // : + for (int i = 0; i < resolvedHandles.Count; i++) { + encoder.LoadArgument (1); + encoder.LoadConstantI4 (i); + // bne.un.s with offset = 7 (ldftn is 2-byte opcode + 4-byte token = 6, ret is 1 byte) + encoder.OpCode (ILOpCode.Bne_un_s); + encoder.CodeBuilder.WriteSByte (7); + encoder.OpCode (ILOpCode.Ldftn); + encoder.Token (resolvedHandles [i]); + encoder.OpCode (ILOpCode.Ret); + } + // Default: return IntPtr.Zero + encoder.OpCode (ILOpCode.Ldc_i4_0); + encoder.OpCode (ILOpCode.Conv_i); + encoder.OpCode (ILOpCode.Ret); + }); + } + void EmitTypeMapAttribute (TypeMapAttributeData entry) { var ctorRef = entry.IsUnconditional ? _typeMapAttrCtorRef2Arg : _typeMapAttrCtorRef3Arg; diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index 4f6c595a5ba..dcd80fbb154 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -504,4 +504,22 @@ public void ParseParameterTypes_UnterminatedSignature_ReturnsEmptyList () { Assert.Empty (JniSignatureHelper.ParseParameterTypes ("(")); } + + [Fact] + public void Generate_AcwProxy_HasGetFunctionPointer () + { + var peers = ScanFixtures (); + var acwPeer = peers.First (p => p.JavaName == "my/app/MainActivity"); + Assert.False (acwPeer.DoNotGenerateAcw); + + using var stream = GenerateAssembly (new [] { acwPeer }, "GetFnPtrTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var methodDefs = reader.MethodDefinitions + .Select (h => reader.GetMethodDefinition (h)) + .Select (m => reader.GetString (m.Name)) + .ToList (); + Assert.Contains ("GetFunctionPointer", methodDefs); + } } \ No newline at end of file