From c1f136f38940cf6d469e18591df9c95b06329f37 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 29 Jun 2026 16:31:11 +0200 Subject: [PATCH 01/16] [Mono.Android] Move Mono.Android.Export rooting off the shared ExportAttribute ExportAttribute is shared with the trimmable type map, which generates JavaPeerProxy and .java for [Export] members and must NOT pull in Mono.Android.Export. So the [DynamicDependency] and [RequiresUnreferencedCode] are removed from ExportAttribute and the dependency is rooted where the managed/reflection [Export] path actually loads it instead: * AndroidRuntime.CreateDynamicCallback gets the [DynamicDependency] on DynamicCallbackCodeGenerator/Mono.Android.Export. It is only kept by the linker on the reflection path; the trimmable path trims it and stays free of the dependency. * AndroidTypeManager is marked [RequiresUnreferencedCode] (it is reflection-backed), replacing the per-method IL2026/IL2075 suppressions; the single resulting cascade in JNIEnvInit.CreateTypeManager is suppressed (that branch is trimmed in trimmable apps). * ExportAttribute drops [RequiresUnreferencedCode] (API breakage baselined). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 8 +++++--- src/Mono.Android/Android.Runtime/JNIEnvInit.cs | 1 + src/Mono.Android/ApiCompatLinesToAdd.txt | 2 ++ .../Java.Interop/ExportAttribute.cs | 6 ------ .../acceptable-breakages-vReference-net11.0.txt | 1 + 5 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 src/Mono.Android/ApiCompatLinesToAdd.txt diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index dd0d94a73a9..2cba09d569c 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -311,6 +311,7 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value) } [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Temporary suppression for Java.Interop reflection manager base.")] + [RequiresUnreferencedCode ("AndroidTypeManager is reflection-backed (it loads Mono.Android.Export for [Export] members) and is not trimming-compatible.")] class AndroidTypeManager : JniRuntime.ReflectionJniTypeManager { bool jniAddNativeMethodRegistrationAttributePresent; @@ -401,9 +402,10 @@ protected override IEnumerable GetSimpleReferences (Type type) static MethodInfo? dynamic_callback_gen; - // See ExportAttribute.cs - [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Mono.Android.Export.dll is preserved when [Export] is used via [DynamicDependency].")] - [UnconditionalSuppressMessage ("Trimming", "IL2075", Justification = "Mono.Android.Export.dll is preserved when [Export] is used via [DynamicDependency].")] + // See ExportAttribute.cs. The [DynamicDependency] roots Mono.Android.Export only when this + // loader is kept by the linker (the managed/reflection [Export] path). The trimmable type map + // generates JavaPeerProxy code instead and never calls this, so it does not pull in the assembly. + [DynamicDependency (DynamicallyAccessedMemberTypes.All, "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")] static Delegate CreateDynamicCallback (MethodInfo method) { if (dynamic_callback_gen == null) { diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index c2e7ea913ca..72333daa5cb 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -168,6 +168,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) [UnmanagedCallConv (CallConvs = new[] { typeof (CallConvCdecl) })] private static unsafe partial void xamarin_app_init (IntPtr env, delegate* unmanaged get_function_pointer); + [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "The AndroidTypeManager branch is only reached when RuntimeFeature.TrimmableTypeMap is false; the linker substitutes the feature switch and trims this branch in trimmable apps.")] internal static JniRuntime.JniTypeManager CreateTypeManager (JnienvInitializeArgs args) { if (RuntimeFeature.TrimmableTypeMap) { diff --git a/src/Mono.Android/ApiCompatLinesToAdd.txt b/src/Mono.Android/ApiCompatLinesToAdd.txt new file mode 100644 index 00000000000..b69aa045665 --- /dev/null +++ b/src/Mono.Android/ApiCompatLinesToAdd.txt @@ -0,0 +1,2 @@ +CannotRemoveAttribute : Attribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute' exists on 'Java.Interop.ExportAttribute' in the contract but not the implementation. +Total Issues: 1 diff --git a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs index 1761bf4466e..8c5efd9cf32 100644 --- a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs +++ b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; namespace Java.Interop { @@ -7,20 +6,15 @@ namespace Java.Interop { [AttributeUsage (AttributeTargets.Method | AttributeTargets.Constructor, AllowMultiple=false, Inherited=false)] -#if !NETSTANDARD2_0 - [RequiresUnreferencedCode ("[ExportAttribute] uses dynamic features.")] -#endif #if !JCW_ONLY_TYPE_NAMES public #endif // !JCW_ONLY_TYPE_NAMES partial class ExportAttribute : Attribute { - [DynamicDependency (DynamicallyAccessedMemberTypes.All, "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")] public ExportAttribute () { } - [DynamicDependency (DynamicallyAccessedMemberTypes.All, "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")] public ExportAttribute (string name) { Name = name; diff --git a/tests/api-compatibility/acceptable-breakages-vReference-net11.0.txt b/tests/api-compatibility/acceptable-breakages-vReference-net11.0.txt index 0feba3a1457..ba9a5add2cf 100644 --- a/tests/api-compatibility/acceptable-breakages-vReference-net11.0.txt +++ b/tests/api-compatibility/acceptable-breakages-vReference-net11.0.txt @@ -48,3 +48,4 @@ MembersMustExist : Member 'public void Android.Telecom.CallControl.RequestVideoS TypesMustExist : Type 'Android.Text.IInputType' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Xamarin.Android.Net.AndroidClientHandler' does not exist in the implementation but it does exist in the contract. CannotRemoveBaseTypeOrInterface : Type 'Android.Views.InputMethods.EditorInfo' does not implement interface 'Android.Text.IInputType' in the implementation but it does in the contract. +CannotRemoveAttribute : Attribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute' exists on 'Java.Interop.ExportAttribute' in the contract but not the implementation. From 35489d9cef8063246f0f8604aac523c625ea7828 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 29 Jun 2026 16:38:18 +0200 Subject: [PATCH 02/16] Drop now-redundant IL2026 suppression on AndroidTypeManager [RequiresUnreferencedCode] on the class already suppresses trim-analysis warnings within its members, so the class-level [UnconditionalSuppressMessage IL2026] is no longer needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 2cba09d569c..90e615ca3a1 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -310,7 +310,6 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value) } } - [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Temporary suppression for Java.Interop reflection manager base.")] [RequiresUnreferencedCode ("AndroidTypeManager is reflection-backed (it loads Mono.Android.Export for [Export] members) and is not trimming-compatible.")] class AndroidTypeManager : JniRuntime.ReflectionJniTypeManager { bool jniAddNativeMethodRegistrationAttributePresent; From 98e01280c2b3a34fafcd7c7017430bab2f2508e0 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 29 Jun 2026 17:08:12 +0200 Subject: [PATCH 03/16] Address review: cover [ExportField], remove stale apicompat file, cleanups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ExportFieldAttribute: drop [DynamicDependency] too — CreateDynamicCallback is the single runtime loader for both [Export] and [ExportField] (__export__ connector), so the loader-side dependency covers it; the shared attributes are now fully trimmable-safe. * Remove the accidentally-committed ApiCompatLinesToAdd.txt (CheckApiCompatibility task output; the breakage is baselined in acceptable-breakages). * CreateDynamicCallback: reword the now-stale 'See ExportAttribute.cs' comment and replace the null-forgiving operator with an explicit 'is not Delegate' check that throws InvalidOperationException, matching the surrounding version-mismatch throws. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 12 ++++++++---- src/Mono.Android/ApiCompatLinesToAdd.txt | 2 -- .../Java.Interop/ExportFieldAttribute.cs | 2 -- 3 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 src/Mono.Android/ApiCompatLinesToAdd.txt diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 90e615ca3a1..8b8ddb0de6a 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -401,9 +401,11 @@ protected override IEnumerable GetSimpleReferences (Type type) static MethodInfo? dynamic_callback_gen; - // See ExportAttribute.cs. The [DynamicDependency] roots Mono.Android.Export only when this - // loader is kept by the linker (the managed/reflection [Export] path). The trimmable type map - // generates JavaPeerProxy code instead and never calls this, so it does not pull in the assembly. + // Roots Mono.Android.Export (and DynamicCallbackCodeGenerator) for the managed/reflection + // [Export] and [ExportField] path. This loader is the single runtime entry point for both + // attributes (their generated members register with the "__export__" connector). It is only + // kept by the linker when that path is reachable; the trimmable type map generates JavaPeerProxy + // and .java code instead and trims this method, so it never pulls in the assembly. [DynamicDependency (DynamicallyAccessedMemberTypes.All, "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")] static Delegate CreateDynamicCallback (MethodInfo method) { @@ -418,7 +420,9 @@ static Delegate CreateDynamicCallback (MethodInfo method) if (dynamic_callback_gen == null) throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required method was not found."); } - return (Delegate)dynamic_callback_gen.Invoke (null, new object [] { method })!; + if (dynamic_callback_gen.Invoke (null, new object [] { method }) is not Delegate callback) + throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. DynamicCallbackCodeGenerator.Create did not return a delegate."); + return callback; } // [Export] callback delegates are created dynamically via DynamicCallbackCodeGenerator and are not diff --git a/src/Mono.Android/ApiCompatLinesToAdd.txt b/src/Mono.Android/ApiCompatLinesToAdd.txt deleted file mode 100644 index b69aa045665..00000000000 --- a/src/Mono.Android/ApiCompatLinesToAdd.txt +++ /dev/null @@ -1,2 +0,0 @@ -CannotRemoveAttribute : Attribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute' exists on 'Java.Interop.ExportAttribute' in the contract but not the implementation. -Total Issues: 1 diff --git a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs index 888636f60f0..753406e2633 100644 --- a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs +++ b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; namespace Java.Interop { @@ -12,7 +11,6 @@ namespace Java.Interop { #endif // !JCW_ONLY_TYPE_NAMES partial class ExportFieldAttribute : Attribute { - [DynamicDependency (DynamicallyAccessedMemberTypes.All, "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")] public ExportFieldAttribute (string name) { Name = name; From 18db45cae0fb070cc16b8d562f14ff73318752c1 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 29 Jun 2026 20:04:12 +0200 Subject: [PATCH 04/16] [tests] Update BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc Rooting Mono.Android.Export (and System.Reflection.Emit via DynamicCallbackCodeGenerator) from AndroidRuntimeManager.CreateDynamicCallback on the managed/reflection path grows the SimpleDotNet.CoreCLR app, so the BuildReleaseArm64(False,CoreCLR) size-regression reference needs refreshing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc index 0d3d9720ca1..889d8460775 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc @@ -8,16 +8,16 @@ "Size": 402352 }, "lib/arm64-v8a/libassembly-store.so": { - "Size": 3461344 + "Size": 5478664 }, "lib/arm64-v8a/libclrjit.so": { - "Size": 2804464 + "Size": 2836936 }, "lib/arm64-v8a/libcoreclr.so": { - "Size": 4872088 + "Size": 4890752 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 1325808 + "Size": 1324320 }, "lib/arm64-v8a/libSystem.Globalization.Native.so": { "Size": 72112 @@ -32,7 +32,7 @@ "Size": 168080 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 20776 + "Size": 29192 }, "res/drawable-hdpi-v4/icon.png": { "Size": 2178 @@ -59,5 +59,5 @@ "Size": 1904 } }, - "PackageSize": 7497147 + "PackageSize": 9209275 } \ No newline at end of file From c1214efd7099cdaa509211301d5b6234edad8baa Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Tue, 30 Jun 2026 18:12:48 +0200 Subject: [PATCH 05/16] [tests] Update BuildReleaseArm64 CoreCLR apkdesc for Export rooting Rooting Mono.Android.Export from the [Export]/[ExportField] loader grows libassembly-store.so and libxamarin-app.so, pushing BuildReleaseArm64 (False,CoreCLR) past the apkdiff size thresholds. Update the reference apkdesc with the expected sizes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc index a00fd898add..cb23d28876b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc @@ -8,7 +8,7 @@ "Size": 402352 }, "lib/arm64-v8a/libassembly-store.so": { - "Size": 3229120 + "Size": 5290544 }, "lib/arm64-v8a/libclrjit.so": { "Size": 2835128 @@ -32,7 +32,7 @@ "Size": 168080 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 20984 + "Size": 29192 }, "res/drawable-hdpi-v4/icon.png": { "Size": 2178 @@ -59,5 +59,5 @@ "Size": 1904 } }, - "PackageSize": 7312827 + "PackageSize": 9049531 } \ No newline at end of file From ff1b627c631ba7e02673e5b9bc2e03d8461ba3d2 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Tue, 30 Jun 2026 18:34:09 +0200 Subject: [PATCH 06/16] [Mono.Android] Pin only DynamicCallbackCodeGenerator.Create, not All The [Export]/[ExportField] loader only reflects on the single public static DynamicCallbackCodeGenerator.Create(MethodInfo) method, so use the (memberSignature, typeName, assemblyName) DynamicDependency overload to pin exactly that member instead of DynamicallyAccessedMemberTypes.All. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 9af73846b9e..437949bca5f 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -397,7 +397,7 @@ protected override IEnumerable GetSimpleReferences (Type type) // attributes (their generated members register with the "__export__" connector). It is only // kept by the linker when that path is reachable; the trimmable type map generates JavaPeerProxy // and .java code instead and trims this method, so it never pulls in the assembly. - [DynamicDependency (DynamicallyAccessedMemberTypes.All, "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")] + [DynamicDependency ("Create", "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")] static Delegate CreateDynamicCallback (MethodInfo method) { if (dynamic_callback_gen == null) { From 52f2ca48e6a8708339cb913af3fd4c0bd1c0f48b Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Tue, 30 Jun 2026 21:18:50 +0200 Subject: [PATCH 07/16] [Mono.Android] Use trimmer-analyzable Type.GetType, drop [DynamicDependency] Replace Assembly.Load ("Mono.Android.Export") + GetType with an assembly-qualified Type.GetType ("..., Mono.Android.Export") feeding a constant GetMethod ("Create"). Both ILLink and the NativeAOT ILC have intrinsics for Type.GetType with a constant string and GetMethod with a constant name on a statically-known type, so the [Export]/[ExportField] codegen entry point is now statically analyzable and the assembly is preserved without the explicit [DynamicDependency] attribute. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Android.Runtime/AndroidRuntime.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 437949bca5f..0caf3ed1958 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -392,21 +392,19 @@ protected override IEnumerable GetSimpleReferences (Type type) static MethodInfo? dynamic_callback_gen; - // Roots Mono.Android.Export (and DynamicCallbackCodeGenerator) for the managed/reflection + // Roots Mono.Android.Export (and DynamicCallbackCodeGenerator.Create) for the managed/reflection // [Export] and [ExportField] path. This loader is the single runtime entry point for both - // attributes (their generated members register with the "__export__" connector). It is only - // kept by the linker when that path is reachable; the trimmable type map generates JavaPeerProxy - // and .java code instead and trims this method, so it never pulls in the assembly. - [DynamicDependency ("Create", "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")] + // attributes (their generated members register with the "__export__" connector). The assembly- + // qualified Type.GetType + constant GetMethod ("Create") is statically analyzable by the trimmer, + // so it keeps Mono.Android.Export and DynamicCallbackCodeGenerator.Create only when this path is + // reachable; the trimmable type map generates JavaPeerProxy and .java code instead and trims this + // method, so it never pulls in the assembly. static Delegate CreateDynamicCallback (MethodInfo method) { if (dynamic_callback_gen == null) { - var assembly = Assembly.Load ("Mono.Android.Export"); - if (assembly == null) - throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); - var type = assembly.GetType ("Java.Interop.DynamicCallbackCodeGenerator"); + var type = Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export"); if (type == null) - throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required type was not found."); + throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); dynamic_callback_gen = type.GetMethod ("Create"); if (dynamic_callback_gen == null) throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required method was not found."); From d4c3b164ca387dd2331f39aa938314e506f0bce7 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Tue, 30 Jun 2026 21:24:37 +0200 Subject: [PATCH 08/16] [Mono.Android] Chain Type.GetType()?.GetMethod() for trimmer dataflow The trimmer only recognizes the GetMethod intrinsic when the constant Type value flows directly into it. Routing the Type.GetType result through an intermediate local + null-check breaks the dataflow, so chain Type.GetType ("...")?.GetMethod ("Create") ?? throw into a single expression. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 0caf3ed1958..b638df38dca 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -402,12 +402,8 @@ protected override IEnumerable GetSimpleReferences (Type type) static Delegate CreateDynamicCallback (MethodInfo method) { if (dynamic_callback_gen == null) { - var type = Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export"); - if (type == null) - throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); - dynamic_callback_gen = type.GetMethod ("Create"); - if (dynamic_callback_gen == null) - throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required method was not found."); + dynamic_callback_gen = Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") + ?? throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); } if (dynamic_callback_gen.Invoke (null, new object [] { method }) is not Delegate callback) throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. DynamicCallbackCodeGenerator.Create did not return a delegate."); From 117fc46d94e19835b29c84bbb7d8e14591d7176d Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Tue, 30 Jun 2026 21:32:28 +0200 Subject: [PATCH 09/16] Simplify --- .../Android.Runtime/AndroidRuntime.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index b638df38dca..24bf8ac2bee 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -392,22 +392,13 @@ protected override IEnumerable GetSimpleReferences (Type type) static MethodInfo? dynamic_callback_gen; - // Roots Mono.Android.Export (and DynamicCallbackCodeGenerator.Create) for the managed/reflection - // [Export] and [ExportField] path. This loader is the single runtime entry point for both - // attributes (their generated members register with the "__export__" connector). The assembly- - // qualified Type.GetType + constant GetMethod ("Create") is statically analyzable by the trimmer, - // so it keeps Mono.Android.Export and DynamicCallbackCodeGenerator.Create only when this path is - // reachable; the trimmable type map generates JavaPeerProxy and .java code instead and trims this - // method, so it never pulls in the assembly. static Delegate CreateDynamicCallback (MethodInfo method) { - if (dynamic_callback_gen == null) { - dynamic_callback_gen = Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") - ?? throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); - } - if (dynamic_callback_gen.Invoke (null, new object [] { method }) is not Delegate callback) - throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. DynamicCallbackCodeGenerator.Create did not return a delegate."); - return callback; + dynamic_callback_gen ??= Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") + ?? throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); + + return dynamic_callback_gen.Invoke (null, [method]) as Delegate + ?? throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. DynamicCallbackCodeGenerator.Create did not return a delegate."); } // [Export] callback delegates are created dynamically via DynamicCallbackCodeGenerator and are not From ffeaf4565f07052f72a3a7dee8342e2f686c4c51 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 1 Jul 2026 10:04:51 +0200 Subject: [PATCH 10/16] Try to allow trimming Mono.Android.Export when it's not needed --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 24bf8ac2bee..bcb1f73fddd 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -394,6 +394,7 @@ protected override IEnumerable GetSimpleReferences (Type type) static Delegate CreateDynamicCallback (MethodInfo method) { + // We're loading the Mono.Android.Export assembly dynamically to avoid problems with circular dependencies. dynamic_callback_gen ??= Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") ?? throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); @@ -534,14 +535,20 @@ public override void RegisterNativeMembers ( var mname = name.Slice (2); MethodInfo? minfo = null; typeMethods ??= type.GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - foreach (var mi in typeMethods) + foreach (var mi in typeMethods) { if (mname.SequenceEqual (mi.Name) && signature.SequenceEqual (JavaNativeTypeManager.GetJniSignature (mi))) { minfo = mi; break; } - + } if (minfo == null) throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' was not found. Signature: {signature.ToString ()}")); + + // This check is for trimming purposes: if `ExportAttribute` is trimmed from the app, so should be `CreateDynamicCallback` + // and with it the implicit dependency on `Mono.Android.Export.dll`. + if (minfo.GetCustomAttribute () is not ExportAttribute) + throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{minfo.Name}' was not marked with [Export] attribute. Signature: {signature.ToString ()}")); + callback = CreateDynamicCallback (minfo); lock (prevent_delegate_gc_lock) { prevent_delegate_gc.Add (callback); From 1863854a949095d8d214e1d0982a590a41d8f17a Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 1 Jul 2026 10:14:23 +0200 Subject: [PATCH 11/16] Change exception message --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index bcb1f73fddd..676ad3bf0f9 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -398,8 +398,8 @@ static Delegate CreateDynamicCallback (MethodInfo method) dynamic_callback_gen ??= Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") ?? throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); - return dynamic_callback_gen.Invoke (null, [method]) as Delegate - ?? throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. DynamicCallbackCodeGenerator.Create did not return a delegate."); + return (Delegate?)dynamic_callback_gen.Invoke (null, [method]) + ?? throw new InvalidOperationException (FormattableString.Invariant ($"Unable to create dynamic callback for method '{method.Name}' on type '{method.DeclaringType?.FullName}'")); } // [Export] callback delegates are created dynamically via DynamicCallbackCodeGenerator and are not From d99cf6afbd155687fa58cc2afb290f3a1cb67bb4 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 2 Jul 2026 22:33:38 +0200 Subject: [PATCH 12/16] Make CreateDynamicDelegate properly trimmable --- .../Android.Runtime/AndroidRuntime.cs | 22 +++--------------- .../PublicAPI/API-35/PublicAPI.Unshipped.txt | 1 + .../API-36.1/PublicAPI.Unshipped.txt | 1 + .../PublicAPI/API-36/PublicAPI.Unshipped.txt | 1 + .../PublicAPI/API-37/PublicAPI.Unshipped.txt | 1 + .../Java.Interop/ExportAttribute.cs | 23 ++++++++++++++++++- 6 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 676ad3bf0f9..51ed16c0ad0 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -390,17 +390,6 @@ protected override IEnumerable GetSimpleReferences (Type type) delegate Delegate GetCallbackHandler (); - static MethodInfo? dynamic_callback_gen; - - static Delegate CreateDynamicCallback (MethodInfo method) - { - // We're loading the Mono.Android.Export assembly dynamically to avoid problems with circular dependencies. - dynamic_callback_gen ??= Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") - ?? throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); - - return (Delegate?)dynamic_callback_gen.Invoke (null, [method]) - ?? throw new InvalidOperationException (FormattableString.Invariant ($"Unable to create dynamic callback for method '{method.Name}' on type '{method.DeclaringType?.FullName}'")); - } // [Export] callback delegates are created dynamically via DynamicCallbackCodeGenerator and are not // cached in static fields (unlike non-[Export] connector delegates). Without rooting them here, @@ -541,15 +530,10 @@ public override void RegisterNativeMembers ( break; } } - if (minfo == null) - throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' was not found. Signature: {signature.ToString ()}")); - - // This check is for trimming purposes: if `ExportAttribute` is trimmed from the app, so should be `CreateDynamicCallback` - // and with it the implicit dependency on `Mono.Android.Export.dll`. - if (minfo.GetCustomAttribute () is not ExportAttribute) - throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{minfo.Name}' was not marked with [Export] attribute. Signature: {signature.ToString ()}")); + + callback = minfo?.GetCustomAttribute ()?.CreateDynamicCallback (minfo) + ?? throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' was not found. Signature: {signature.ToString ()}")); - callback = CreateDynamicCallback (minfo); lock (prevent_delegate_gc_lock) { prevent_delegate_gc.Add (callback); } diff --git a/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt index 75505207238..867f297909b 100644 --- a/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt @@ -25,6 +25,7 @@ REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void +Java.Interop.BaseExportAttribute Java.Interop.JavaArrayProxy abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array! abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]! diff --git a/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt index 75505207238..867f297909b 100644 --- a/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt @@ -25,6 +25,7 @@ REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void +Java.Interop.BaseExportAttribute Java.Interop.JavaArrayProxy abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array! abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]! diff --git a/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt index 4def2ad9232..45e15c9a177 100644 --- a/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt @@ -4401,6 +4401,7 @@ virtual Java.Util.IdentityHashMap.Remove(Java.Lang.Object? key, Java.Lang.Object virtual Java.Util.IdentityHashMap.Replace(Java.Lang.Object? key, Java.Lang.Object? oldValue, Java.Lang.Object? newValue) -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void +Java.Interop.BaseExportAttribute Java.Interop.JavaArrayProxy abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array! abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]! diff --git a/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt index 75505207238..867f297909b 100644 --- a/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt @@ -25,6 +25,7 @@ REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void +Java.Interop.BaseExportAttribute Java.Interop.JavaArrayProxy abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array! abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]! diff --git a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs index 8c5efd9cf32..f5d5dd1dd49 100644 --- a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs +++ b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace Java.Interop { @@ -9,7 +10,7 @@ namespace Java.Interop { #if !JCW_ONLY_TYPE_NAMES public #endif // !JCW_ONLY_TYPE_NAMES - partial class ExportAttribute : Attribute { + partial class ExportAttribute : BaseExportAttribute { public ExportAttribute () { @@ -24,6 +25,26 @@ public ExportAttribute (string name) public string? SuperArgumentsString {get; set;} public Type []? Throws {get; set;} internal string []? ThrownNames {get; set;} // msbuild internal use + + static MethodInfo? dynamic_callback_gen; + + internal override Delegate CreateDynamicCallback (MethodInfo method) + { + // We're loading the Mono.Android.Export assembly dynamically to avoid problems with circular dependencies. + dynamic_callback_gen ??= Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") + ?? throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); + + return (Delegate?)dynamic_callback_gen.Invoke (null, [method]) + ?? throw new InvalidOperationException (FormattableString.Invariant ($"Unable to create dynamic callback for method '{method.Name}' on type '{method.DeclaringType?.FullName}'")); + } + } + +#if !JCW_ONLY_TYPE_NAMES + public +#endif // !JCW_ONLY_TYPE_NAMES + abstract class BaseExportAttribute : Attribute + { + internal abstract Delegate CreateDynamicCallback (MethodInfo method); } } From ca5dc52cbd4a437956ed7978a86c5de0a01688e7 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 3 Jul 2026 07:32:52 +0200 Subject: [PATCH 13/16] [tests] Use smaller apkdesc sizes for SimpleDotNet.CoreCLR Prefer main's Zstd-compressed reference sizes over the branch's larger LZ4 values when resolving the merge conflict. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc index cb23d28876b..065272635fa 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc @@ -8,7 +8,7 @@ "Size": 402352 }, "lib/arm64-v8a/libassembly-store.so": { - "Size": 5290544 + "Size": 2717688 }, "lib/arm64-v8a/libclrjit.so": { "Size": 2835128 @@ -32,7 +32,7 @@ "Size": 168080 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 29192 + "Size": 20968 }, "res/drawable-hdpi-v4/icon.png": { "Size": 2178 @@ -59,5 +59,5 @@ "Size": 1904 } }, - "PackageSize": 9049531 + "PackageSize": 7271867 } \ No newline at end of file From 1c943299d9f5de54a51ceedb544184166ff745ae Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 3 Jul 2026 10:32:46 +0200 Subject: [PATCH 14/16] Code cleanup --- .../Android.Runtime/AndroidRuntime.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 07d813799cf..34199244c40 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -315,7 +315,7 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value) } [RequiresDynamicCode ("This type manager is reflection-backed and is not compatible with Native AOT.")] - [RequiresUnreferencedCode ("AndroidTypeManager is reflection-backed (it loads Mono.Android.Export for [Export] members) and is not trimming-compatible.")] + [RequiresUnreferencedCode ("This type manager is reflection-backed and is not trimming-compatible.")] class AndroidTypeManager : JniRuntime.ReflectionJniTypeManager { bool jniAddNativeMethodRegistrationAttributePresent; @@ -520,16 +520,19 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, Read var mname = name.Slice (2); MethodInfo? minfo = null; typeMethods ??= type.GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - foreach (var mi in typeMethods) { + foreach (var mi in typeMethods) if (mname.SequenceEqual (mi.Name) && signature.SequenceEqual (JavaNativeTypeManager.GetJniSignature (mi))) { minfo = mi; break; } - } - - callback = minfo?.GetCustomAttribute ()?.CreateDynamicCallback (minfo) - ?? throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' was not found. Signature: {signature.ToString ()}")); + if (minfo == null) + throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' was not found. Signature: {signature.ToString ()}")); + + var exportAttribute = minfo.GetCustomAttribute () + ?? throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' does not have [Export] attribute. Signature: {signature.ToString ()}")); + + callback = exportAttribute.CreateDynamicCallback (minfo); lock (prevent_delegate_gc_lock) { prevent_delegate_gc.Add (callback); } From debe97a111dc3a591c087a2b7908bdffc318a036 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 3 Jul 2026 10:55:24 +0200 Subject: [PATCH 15/16] Extend support to ExportFieldAttribute --- .../Java.Interop/BaseExportAttribute.cs | 24 +++++++++++++++++++ .../Java.Interop/ExportAttribute.cs | 17 +------------ .../Java.Interop/ExportFieldAttribute.cs | 10 +++++--- ...n.Android.NamingCustomAttributes.projitems | 1 + 4 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 src/Xamarin.Android.NamingCustomAttributes/Java.Interop/BaseExportAttribute.cs diff --git a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/BaseExportAttribute.cs b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/BaseExportAttribute.cs new file mode 100644 index 00000000000..81a31df047f --- /dev/null +++ b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/BaseExportAttribute.cs @@ -0,0 +1,24 @@ +using System; +using System.Reflection; + +namespace Java.Interop; + +#if !JCW_ONLY_TYPE_NAMES +public +#endif // !JCW_ONLY_TYPE_NAMES +abstract class BaseExportAttribute : Attribute +{ + internal abstract Delegate CreateDynamicCallback (MethodInfo method); + + static MethodInfo? dynamic_callback_gen; + + private protected static Delegate CreateDynamicCallbackCore (MethodInfo method) + { + // We're loading the Mono.Android.Export assembly dynamically to avoid problems with circular dependencies. + dynamic_callback_gen ??= Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") + ?? throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); + + return (Delegate?)dynamic_callback_gen.Invoke (null, [method]) + ?? throw new InvalidOperationException (FormattableString.Invariant ($"Unable to create dynamic callback for method '{method.Name}' on type '{method.DeclaringType?.FullName}'")); + } +} diff --git a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs index f5d5dd1dd49..8618d5a058f 100644 --- a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs +++ b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportAttribute.cs @@ -26,26 +26,11 @@ public ExportAttribute (string name) public Type []? Throws {get; set;} internal string []? ThrownNames {get; set;} // msbuild internal use - static MethodInfo? dynamic_callback_gen; - internal override Delegate CreateDynamicCallback (MethodInfo method) { - // We're loading the Mono.Android.Export assembly dynamically to avoid problems with circular dependencies. - dynamic_callback_gen ??= Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") - ?? throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); - - return (Delegate?)dynamic_callback_gen.Invoke (null, [method]) - ?? throw new InvalidOperationException (FormattableString.Invariant ($"Unable to create dynamic callback for method '{method.Name}' on type '{method.DeclaringType?.FullName}'")); + return CreateDynamicCallbackCore (method); } } - -#if !JCW_ONLY_TYPE_NAMES - public -#endif // !JCW_ONLY_TYPE_NAMES - abstract class BaseExportAttribute : Attribute - { - internal abstract Delegate CreateDynamicCallback (MethodInfo method); - } } diff --git a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs index 753406e2633..36f1ad25188 100644 --- a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs +++ b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace Java.Interop { @@ -9,7 +10,7 @@ namespace Java.Interop { #if !JCW_ONLY_TYPE_NAMES public #endif // !JCW_ONLY_TYPE_NAMES - partial class ExportFieldAttribute : Attribute { + partial class ExportFieldAttribute : BaseExportAttribute { public ExportFieldAttribute (string name) { @@ -17,7 +18,10 @@ public ExportFieldAttribute (string name) } public string Name {get; set;} + + internal override Delegate CreateDynamicCallback (MethodInfo method) + { + return CreateDynamicCallbackCore (method); + } } } - - diff --git a/src/Xamarin.Android.NamingCustomAttributes/Xamarin.Android.NamingCustomAttributes.projitems b/src/Xamarin.Android.NamingCustomAttributes/Xamarin.Android.NamingCustomAttributes.projitems index ccfa68d876d..3deec04673e 100644 --- a/src/Xamarin.Android.NamingCustomAttributes/Xamarin.Android.NamingCustomAttributes.projitems +++ b/src/Xamarin.Android.NamingCustomAttributes/Xamarin.Android.NamingCustomAttributes.projitems @@ -14,6 +14,7 @@ + From 26aab7d1df8f808dc78395e73e82284ed56fb33e Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 3 Jul 2026 11:01:34 +0200 Subject: [PATCH 16/16] Clarify Mono.Android.Export load failure diagnostic Address review feedback on the folded Type.GetType(...)?.GetMethod(...) loader: a present-but-stale Mono.Android.Export.dll (missing the Create method) previously reported the misleading "needs to be referenced" message. The chain must stay a single expression for trimmer dataflow (see d4c3b164c), so instead broaden the message to cover both the not-referenced and version-mismatch cases, and note it applies to [Export] and [ExportField]. Also use tabs per repo style and document why the call is chained. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/BaseExportAttribute.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/BaseExportAttribute.cs b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/BaseExportAttribute.cs index 81a31df047f..1e0d164a788 100644 --- a/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/BaseExportAttribute.cs +++ b/src/Xamarin.Android.NamingCustomAttributes/Java.Interop/BaseExportAttribute.cs @@ -8,17 +8,19 @@ namespace Java.Interop; #endif // !JCW_ONLY_TYPE_NAMES abstract class BaseExportAttribute : Attribute { - internal abstract Delegate CreateDynamicCallback (MethodInfo method); + internal abstract Delegate CreateDynamicCallback (MethodInfo method); - static MethodInfo? dynamic_callback_gen; + static MethodInfo? dynamic_callback_gen; - private protected static Delegate CreateDynamicCallbackCore (MethodInfo method) - { - // We're loading the Mono.Android.Export assembly dynamically to avoid problems with circular dependencies. - dynamic_callback_gen ??= Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") - ?? throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); + private protected static Delegate CreateDynamicCallbackCore (MethodInfo method) + { + // We're loading the Mono.Android.Export assembly dynamically to avoid problems with circular dependencies. + // The Type.GetType (...)?.GetMethod (...) call is chained intentionally: the trimmer only recognizes the + // GetMethod intrinsic when the constant Type value flows directly into it, so routing it through a local breaks dataflow. + dynamic_callback_gen ??= Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create") + ?? throw new InvalidOperationException ("To use methods marked with [Export] or [ExportField], Mono.Android.Export.dll must be referenced by the application and contain a matching Java.Interop.DynamicCallbackCodeGenerator.Create method."); - return (Delegate?)dynamic_callback_gen.Invoke (null, [method]) - ?? throw new InvalidOperationException (FormattableString.Invariant ($"Unable to create dynamic callback for method '{method.Name}' on type '{method.DeclaringType?.FullName}'")); - } + return (Delegate?)dynamic_callback_gen.Invoke (null, [method]) + ?? throw new InvalidOperationException (FormattableString.Invariant ($"Unable to create dynamic callback for method '{method.Name}' on type '{method.DeclaringType?.FullName}'")); + } }