From c31cd8681c3eb348878a2c40986c4af881b823e3 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 28 Jan 2025 09:14:13 -0600 Subject: [PATCH 01/15] [nativeaot] support for `Application` subclasses --- samples/NativeAOT/MainActivity.cs | 3 ++ samples/NativeAOT/MainApplication.cs | 22 ++++++++++++ .../NativeAOT/NativeAotRuntimeProvider.java | 2 ++ samples/NativeAOT/NativeAotTypeManager.cs | 1 + .../Resources/ApplicationRegistration.java | 2 +- .../Tasks/GenerateJavaStubs.cs | 36 +++++++++++-------- 6 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 samples/NativeAOT/MainApplication.cs diff --git a/samples/NativeAOT/MainActivity.cs b/samples/NativeAOT/MainActivity.cs index 545983d62fe..5b642792108 100644 --- a/samples/NativeAOT/MainActivity.cs +++ b/samples/NativeAOT/MainActivity.cs @@ -1,4 +1,5 @@ using Android.Runtime; +using Android.Util; using System.Reflection; using System.Runtime.InteropServices; @@ -10,6 +11,8 @@ public class MainActivity : Activity { protected override void OnCreate(Bundle? savedInstanceState) { + Log.Debug ("NativeAOT", "Application.OnCreate()"); + base.OnCreate(savedInstanceState); // Set our view from the "main" layout resource diff --git a/samples/NativeAOT/MainApplication.cs b/samples/NativeAOT/MainApplication.cs new file mode 100644 index 00000000000..9bfe73a23b7 --- /dev/null +++ b/samples/NativeAOT/MainApplication.cs @@ -0,0 +1,22 @@ +using Android.Runtime; +using Android.Util; + +/// +/// NOTE: This class is not required, but used for testing Android.App.Application subclasses. +/// +[Register ("my/MainApplication")] // Required for typemap in NativeAotTypeManager +[Application] +public class MainApplication : Application +{ + public MainApplication (IntPtr handle, JniHandleOwnership transfer) + : base (handle, transfer) + { + } + + public override void OnCreate () + { + Log.Debug ("NativeAOT", "Application.OnCreate()"); + + base.OnCreate (); + } +} diff --git a/samples/NativeAOT/NativeAotRuntimeProvider.java b/samples/NativeAOT/NativeAotRuntimeProvider.java index b87f23f25c7..caddba69778 100644 --- a/samples/NativeAOT/NativeAotRuntimeProvider.java +++ b/samples/NativeAOT/NativeAotRuntimeProvider.java @@ -21,6 +21,8 @@ public boolean onCreate() { public void attachInfo(android.content.Context context, android.content.pm.ProviderInfo info) { Log.d(TAG, "NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()…"); JavaInteropRuntime.init(); + // NOTE: only required for custom applications + net.dot.jni.ApplicationRegistration.registerApplications(); super.attachInfo (context, info); } diff --git a/samples/NativeAOT/NativeAotTypeManager.cs b/samples/NativeAOT/NativeAotTypeManager.cs index 29499d17847..2649e912efd 100644 --- a/samples/NativeAOT/NativeAotTypeManager.cs +++ b/samples/NativeAOT/NativeAotTypeManager.cs @@ -19,6 +19,7 @@ partial class NativeAotTypeManager : JniRuntime.JniTypeManager { ["android/os/Bundle"] = typeof (Android.OS.Bundle), ["android/view/ContextThemeWrapper"] = typeof (Android.Views.ContextThemeWrapper), ["my/MainActivity"] = typeof (MainActivity), + ["my/MainApplication"] = typeof (MainApplication), }; public NativeAotTypeManager () diff --git a/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java b/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java index 5f86173d6cd..a11f1ea15e3 100644 --- a/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java +++ b/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java @@ -1,4 +1,4 @@ -package mono.android.app; +package JAVA_PACKAGE_NAME; public class ApplicationRegistration { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 766ec7c1465..9ea07fe12a9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -300,19 +300,19 @@ Dictionary MaybeGetArchAssemblies (Dictionary additionalProviders) { - if (androidRuntime != Xamarin.Android.Tasks.AndroidRuntime.MonoVM) { + bool isMonoVm = androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.MonoVM; + if (!isMonoVm) { Log.LogDebugMessage ($"Skipping MonoRuntimeProvider generation for: {androidRuntime}"); - return; - } - - // Create additional runtime provider java sources. - string providerTemplateFile = "MonoRuntimeProvider.Bundled.java"; - string providerTemplate = GetResource (providerTemplateFile); - - foreach (var provider in additionalProviders) { - var contents = providerTemplate.Replace ("MonoRuntimeProvider", provider); - var real_provider = Path.Combine (OutputDirectory, "src", "mono", provider + ".java"); - Files.CopyIfStringChanged (contents, real_provider); + } else { + // Create additional runtime provider java sources. + string providerTemplateFile = "MonoRuntimeProvider.Bundled.java"; + string providerTemplate = GetResource (providerTemplateFile); + + foreach (var provider in additionalProviders) { + var contents = providerTemplate.Replace ("MonoRuntimeProvider", provider); + var real_provider = Path.Combine (OutputDirectory, "src", "mono", provider + ".java"); + Files.CopyIfStringChanged (contents, real_provider); + } } // Create additional application java sources. @@ -326,7 +326,9 @@ void GenerateAdditionalProviderSources (NativeCodeGenState codeGenState, IList template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ()) + template => template + .Replace ("JAVA_PACKAGE_NAME", isMonoVm ? "mono.android.app" : "net.dot.jni") + .Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ()) ); } From 8be4ee6228ad30388ffdf05363e86aa08ab0ec7a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 28 Jan 2025 11:04:24 -0600 Subject: [PATCH 02/15] Address feedback --- .../Microsoft.Android.Sdk.NativeAOT.targets | 1 + .../Resources/ApplicationRegistration.java | 2 +- .../Tasks/GenerateJavaStubs.cs | 19 ++++++++++--------- .../Xamarin.Android.Build.Tests/BuildTest.cs | 4 ++-- .../Utilities/MonoAndroidHelper.cs | 10 ++++++++++ .../Xamarin.Android.Common.targets | 1 + src/java-runtime/java-runtime.targets | 2 +- .../java/mono/android/MonoPackageManager.java | 2 +- .../dot}/ApplicationRegistration.java | 2 +- 9 files changed, 28 insertions(+), 15 deletions(-) rename src/java-runtime/java/{mono/android/app => net/dot}/ApplicationRegistration.java (85%) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index 685e42403f5..64dd729f9ed 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -12,6 +12,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. <_AndroidRuntimePackRuntime>NativeAOT + <_AndroidCodeGenerationTarget Condition=" '$(_AndroidCodeGenerationTarget)' == '' ">JavaInterop1 true diff --git a/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java b/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java index a11f1ea15e3..9f4208e5146 100644 --- a/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java +++ b/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java @@ -1,4 +1,4 @@ -package JAVA_PACKAGE_NAME; +package net.dot.jni; public class ApplicationRegistration { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 9ea07fe12a9..4c4d5a61905 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -96,7 +96,11 @@ public class GenerateJavaStubs : AndroidTask [Required] public string AndroidRuntime { get; set; } = ""; + [Required] + public string CodeGenerationTarget { get; set; } = ""; + AndroidRuntime androidRuntime; + JavaPeerStyle codeGenerationTarget; internal const string AndroidSkipJavaStubGeneration = "AndroidSkipJavaStubGeneration"; @@ -104,6 +108,7 @@ public override bool RunTask () { try { androidRuntime = MonoAndroidHelper.ParseAndroidRuntime (AndroidRuntime); + codeGenerationTarget = MonoAndroidHelper.ParseCodeGenerationTarget (CodeGenerationTarget); bool useMarshalMethods = !Debug && EnableMarshalMethods; Run (useMarshalMethods); } catch (XamarinAndroidException e) { @@ -317,7 +322,7 @@ void GenerateAdditionalProviderSources (NativeCodeGenState codeGenState, IList template - .Replace ("JAVA_PACKAGE_NAME", isMonoVm ? "mono.android.app" : "net.dot.jni") - .Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ()) + template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ()) ); } @@ -398,7 +399,7 @@ IList MergeManifest (NativeCodeGenState codeGenState, Dictionary allJavaTypes, List javaTypesForJCW) = ScanForJavaTypes (resolver, tdCache, assemblies, userAssemblies, useMarshalMethods); var jcwContext = new JCWGeneratorContext (arch, resolver, assemblies.Values, javaTypesForJCW, tdCache, useMarshalMethods); var jcwGenerator = new JCWGenerator (Log, jcwContext) { - CodeGenerationTarget = androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.MonoVM ? JavaPeerStyle.XAJavaInterop1 : JavaPeerStyle.JavaInterop1 + CodeGenerationTarget = codeGenerationTarget, }; bool success; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs index 48c1ed3685b..0327225f9ed 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs @@ -1093,12 +1093,12 @@ public void RemoveOldMonoPackageManager () }; var intermediate = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); var oldMonoPackageManager = Path.Combine (intermediate, "android", "src", "mono", "MonoPackageManager.java"); - var notifyTimeZoneChanges = Path.Combine (intermediate, "android", "src", "mono", "android", "app", "NotifyTimeZoneChanges.java"); + var notifyTimeZoneChanges = Path.Combine (intermediate, "android", "src", "net", "dot", "jni", "NotifyTimeZoneChanges.java"); Directory.CreateDirectory (Path.GetDirectoryName (notifyTimeZoneChanges)); File.WriteAllText (oldMonoPackageManager, @"package mono; public class MonoPackageManager { } class MonoPackageManager_Resources { }"); - File.WriteAllText (notifyTimeZoneChanges, @"package mono.android.app; + File.WriteAllText (notifyTimeZoneChanges, @"package net.dot.jni; public class ApplicationRegistration { }"); var oldMonoPackageManagerClass = Path.Combine (intermediate, "android", "bin", "classes" , "mono", "MonoPackageManager.class"); File.WriteAllText (oldMonoPackageManagerClass, ""); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 1367ded5abf..5659fba2d37 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -9,6 +9,7 @@ using System.Threading; using Xamarin.Android.Tools; using Xamarin.Tools.Zip; +using Java.Interop.Tools.JavaCallableWrappers; #if MSBUILD using Microsoft.Android.Build.Tasks; @@ -830,5 +831,14 @@ public static AndroidRuntime ParseAndroidRuntime (string androidRuntime) // Default runtime is MonoVM return AndroidRuntime.MonoVM; } + + public static JavaPeerStyle ParseCodeGenerationTarget (string codeGenerationTarget) + { + if (Enum.TryParse (codeGenerationTarget, ignoreCase: true, out JavaPeerStyle style)) + return style; + + // Default is XAJavaInterop1 + return JavaPeerStyle.XAJavaInterop1; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 84bc8a00c9a..2334a8c6572 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1501,6 +1501,7 @@ because xbuild doesn't support framework reference assemblies. - + Date: Tue, 28 Jan 2025 11:05:35 -0600 Subject: [PATCH 03/15] Update MainActivity.cs --- samples/NativeAOT/MainActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/NativeAOT/MainActivity.cs b/samples/NativeAOT/MainActivity.cs index 5b642792108..5fdcfd24922 100644 --- a/samples/NativeAOT/MainActivity.cs +++ b/samples/NativeAOT/MainActivity.cs @@ -11,7 +11,7 @@ public class MainActivity : Activity { protected override void OnCreate(Bundle? savedInstanceState) { - Log.Debug ("NativeAOT", "Application.OnCreate()"); + Log.Debug ("NativeAOT", "MainActivity.OnCreate()"); base.OnCreate(savedInstanceState); From 1d6d4c3e9675cb55e522c55ed72d555a1d450493 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 28 Jan 2025 14:26:06 -0600 Subject: [PATCH 04/15] CodeGenerationTarget allowed to be blank --- src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 4c4d5a61905..58a3b7e1140 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -96,7 +96,6 @@ public class GenerateJavaStubs : AndroidTask [Required] public string AndroidRuntime { get; set; } = ""; - [Required] public string CodeGenerationTarget { get; set; } = ""; AndroidRuntime androidRuntime; From 572399e9613b286251eed622c939bd31151d35da Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 29 Jan 2025 15:25:00 -0600 Subject: [PATCH 05/15] Update NativeAotTypeManager.cs --- samples/NativeAOT/NativeAotTypeManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/NativeAOT/NativeAotTypeManager.cs b/samples/NativeAOT/NativeAotTypeManager.cs index 2649e912efd..e8d06af9911 100644 --- a/samples/NativeAOT/NativeAotTypeManager.cs +++ b/samples/NativeAOT/NativeAotTypeManager.cs @@ -13,6 +13,7 @@ partial class NativeAotTypeManager : JniRuntime.JniTypeManager { // TODO: list of types specific to this application Dictionary typeMappings = new () { ["android/app/Activity"] = typeof (Android.App.Activity), + ["android/app/Application"] = typeof (Android.App.Application), ["android/content/Context"] = typeof (Android.Content.Context), ["android/content/ContextWrapper"] = typeof (Android.Content.ContextWrapper), ["android/os/BaseBundle"] = typeof (Android.OS.BaseBundle), From 4f0403893c3ada21888c6552bd3ce1aef19fbd49 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 29 Jan 2025 16:08:42 -0600 Subject: [PATCH 06/15] `net.dot.jni` -> `net.dot.android` --- samples/NativeAOT/NativeAotRuntimeProvider.java | 2 +- .../Resources/ApplicationRegistration.java | 2 +- src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs | 2 +- .../Tests/Xamarin.Android.Build.Tests/BuildTest.cs | 4 ++-- src/java-runtime/java-runtime.targets | 2 +- src/java-runtime/java/mono/android/MonoPackageManager.java | 2 +- .../java/net/dot/{ => android}/ApplicationRegistration.java | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) rename src/java-runtime/java/net/dot/{ => android}/ApplicationRegistration.java (86%) diff --git a/samples/NativeAOT/NativeAotRuntimeProvider.java b/samples/NativeAOT/NativeAotRuntimeProvider.java index caddba69778..6de591501d5 100644 --- a/samples/NativeAOT/NativeAotRuntimeProvider.java +++ b/samples/NativeAOT/NativeAotRuntimeProvider.java @@ -22,7 +22,7 @@ public void attachInfo(android.content.Context context, android.content.pm.Provi Log.d(TAG, "NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()…"); JavaInteropRuntime.init(); // NOTE: only required for custom applications - net.dot.jni.ApplicationRegistration.registerApplications(); + net.dot.android.ApplicationRegistration.registerApplications(); super.attachInfo (context, info); } diff --git a/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java b/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java index 9f4208e5146..f14f681c094 100644 --- a/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java +++ b/src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java @@ -1,4 +1,4 @@ -package net.dot.jni; +package net.dot.android; public class ApplicationRegistration { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 58a3b7e1140..f37bceb44ec 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -340,7 +340,7 @@ void GenerateAdditionalProviderSources (NativeCodeGenState codeGenState, IList - + Date: Wed, 29 Jan 2025 16:11:28 -0600 Subject: [PATCH 07/15] Update GenerateJavaStubs.cs --- src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index f37bceb44ec..2fa90dca06e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -304,8 +304,7 @@ Dictionary MaybeGetArchAssemblies (Dictionary additionalProviders) { - bool isMonoVm = androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.MonoVM; - if (!isMonoVm) { + if (androidRuntime != Xamarin.Android.Tasks.AndroidRuntime.MonoVM) { Log.LogDebugMessage ($"Skipping MonoRuntimeProvider generation for: {androidRuntime}"); } else { // Create additional runtime provider java sources. From f3c122ee1b3d53918b990bbc9ac76b5fd4460499 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 30 Jan 2025 10:49:05 -0500 Subject: [PATCH 08/15] Update Object.GetObject() to use JniRuntime.JniValueManager Should fix the unexpected P/Invoke which is causing the NativeAOT sample to crash: net.dot.jni.internal.JavaProxyThrowable: System.DllNotFoundException: DllNotFound_Linux, xa-internal-api, dlopen failed: library "xa-internal-api.so" not found dlopen failed: library "libxa-internal-api.so" not found dlopen failed: library "xa-internal-api" not found dlopen failed: library "libxa-internal-api" not found 01-30 02:19:25.570 3348 3348 E AndroidRuntime: at System.Runtime.InteropServices.NativeLibrary.LoadLibErrorTracker.Throw(String) + 0x47 at Internal.Runtime.CompilerHelpers.InteropHelpers.FixupModuleCell(InteropHelpers.ModuleFixupCell*) + 0xe2 at Internal.Runtime.CompilerHelpers.InteropHelpers.ResolvePInvokeSlow(InteropHelpers.MethodFixupCell*) + 0x35 at Android.Runtime.RuntimeNativeMethods.monodroid_TypeManager_get_java_class_name(IntPtr) + 0x22 at Java.Interop.TypeManager.GetClassName(IntPtr) + 0xe at Java.Interop.TypeManager.CreateInstance(IntPtr, JniHandleOwnership, Type) + 0x69 at Java.Lang.Object._GetObject[T](IntPtr, JniHandleOwnership) + 0x4e at Android.App.Application.n_OnCreate(IntPtr jnienv, IntPtr native__this) + 0x89 at my.MainApplication.n_onCreate(Native Method) at my.MainApplication.onCreate(MainApplication.java:24) at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1182) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6460) at android.app.ActivityThread.access$1300(ActivityThread.java:219) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) --- src/Mono.Android/Java.Lang/Object.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Mono.Android/Java.Lang/Object.cs b/src/Mono.Android/Java.Lang/Object.cs index fe6b49a8abf..5b348d13b4d 100644 --- a/src/Mono.Android/Java.Lang/Object.cs +++ b/src/Mono.Android/Java.Lang/Object.cs @@ -156,13 +156,9 @@ protected void SetHandle (IntPtr value, JniHandleOwnership transfer) if (handle == IntPtr.Zero) return null; - var r = PeekObject (handle, type); - if (r != null) { - JNIEnv.DeleteRef (handle, transfer); - return r; - } - - return Java.Interop.TypeManager.CreateInstance (handle, transfer, type); + var p = JNIEnvInit.ValueManager!.GetPeer (new JniObjectReference (handle), type); + JNIEnv.DeleteRef (handle, transfer); + return p; } [EditorBrowsable (EditorBrowsableState.Never)] From 7376e7290a662946da1dd9248e0d2a66e667b2e3 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 30 Jan 2025 11:19:22 -0500 Subject: [PATCH 09/15] Revert "Update Object.GetObject() to use JniRuntime.JniValueManager" This reverts commit f3c122ee1b3d53918b990bbc9ac76b5fd4460499. This doens't build, because d3cde470 turns all warnings into errors, and this commit introduces a warning. PR #9728 is the proper fix. --- src/Mono.Android/Java.Lang/Object.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Mono.Android/Java.Lang/Object.cs b/src/Mono.Android/Java.Lang/Object.cs index 5b348d13b4d..fe6b49a8abf 100644 --- a/src/Mono.Android/Java.Lang/Object.cs +++ b/src/Mono.Android/Java.Lang/Object.cs @@ -156,9 +156,13 @@ protected void SetHandle (IntPtr value, JniHandleOwnership transfer) if (handle == IntPtr.Zero) return null; - var p = JNIEnvInit.ValueManager!.GetPeer (new JniObjectReference (handle), type); - JNIEnv.DeleteRef (handle, transfer); - return p; + var r = PeekObject (handle, type); + if (r != null) { + JNIEnv.DeleteRef (handle, transfer); + return r; + } + + return Java.Interop.TypeManager.CreateInstance (handle, transfer, type); } [EditorBrowsable (EditorBrowsableState.Never)] From 84089ed6fd5affa9252a8539436305a791acd39e Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 30 Jan 2025 13:55:39 -0600 Subject: [PATCH 10/15] New NativeAotValueManager --- samples/NativeAOT/JavaInteropRuntime.cs | 5 +- samples/NativeAOT/NativeAotValueManager.cs | 353 ++++++++++++++++++++- 2 files changed, 353 insertions(+), 5 deletions(-) diff --git a/samples/NativeAOT/JavaInteropRuntime.cs b/samples/NativeAOT/JavaInteropRuntime.cs index 4e54afeb3ba..8c86e5d264b 100644 --- a/samples/NativeAOT/JavaInteropRuntime.cs +++ b/samples/NativeAOT/JavaInteropRuntime.cs @@ -34,10 +34,11 @@ static void JNI_OnUnload (IntPtr vm, IntPtr reserved) static void init (IntPtr jnienv, IntPtr klass) { try { + var typeManager = new NativeAotTypeManager (); var options = new JreRuntimeOptions { EnvironmentPointer = jnienv, - TypeManager = new NativeAotTypeManager (), - ValueManager = new NativeAotValueManager (), + TypeManager = typeManager, + ValueManager = new NativeAotValueManager (typeManager), UseMarshalMemberBuilder = false, JniGlobalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:GREF"), JniLocalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:LREF"), diff --git a/samples/NativeAOT/NativeAotValueManager.cs b/samples/NativeAOT/NativeAotValueManager.cs index 8fb2c55d4e2..760de0a74fd 100644 --- a/samples/NativeAOT/NativeAotValueManager.cs +++ b/samples/NativeAOT/NativeAotValueManager.cs @@ -1,10 +1,357 @@ +// Originally from: https://github.com/dotnet/java-interop/blob/9b1d8781e8e322849d05efac32119c913b21c192/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; using Android.Runtime; +using Java.Interop; namespace NativeAOT; -// TODO: make a NativeAOT-specific implementation of AndroidValueManager -// This is enough for "Hello World" to launch -internal class NativeAotValueManager : AndroidValueManager +internal class NativeAotValueManager : JniRuntime.JniValueManager { + readonly NativeAotTypeManager TypeManager; + Dictionary>? RegisteredInstances = new Dictionary>(); + public NativeAotValueManager(NativeAotTypeManager typeManager) => + TypeManager = typeManager; + + public override void WaitForGCBridgeProcessing () + { + } + + public override void CollectPeers () + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (NativeAotValueManager)); + + var peers = new List (); + + lock (RegisteredInstances) { + foreach (var ps in RegisteredInstances.Values) { + foreach (var p in ps) { + peers.Add (p); + } + } + RegisteredInstances.Clear (); + } + List? exceptions = null; + foreach (var peer in peers) { + try { + peer.Dispose (); + } + catch (Exception e) { + exceptions = exceptions ?? new List (); + exceptions.Add (e); + } + } + if (exceptions != null) + throw new AggregateException ("Exceptions while collecting peers.", exceptions); + } + + public override void AddPeer (IJavaPeerable value) + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (NativeAotValueManager)); + + var r = value.PeerReference; + if (!r.IsValid) + throw new ObjectDisposedException (value.GetType ().FullName); + var o = PeekPeer (value.PeerReference); + if (o != null) + return; + + if (r.Type != JniObjectReferenceType.Global) { + value.SetPeerReference (r.NewGlobalRef ()); + JniObjectReference.Dispose (ref r, JniObjectReferenceOptions.CopyAndDispose); + } + int key = value.JniIdentityHashCode; + lock (RegisteredInstances) { + List? peers; + if (!RegisteredInstances.TryGetValue (key, out peers)) { + peers = new List () { + value, + }; + RegisteredInstances.Add (key, peers); + return; + } + + for (int i = peers.Count - 1; i >= 0; i--) { + var p = peers [i]; + if (!JniEnvironment.Types.IsSameObject (p.PeerReference, value.PeerReference)) + continue; + if (Replaceable (p)) { + peers [i] = value; + } else { + WarnNotReplacing (key, value, p); + } + return; + } + peers.Add (value); + } + } + + static bool Replaceable (IJavaPeerable peer) + { + if (peer == null) + return true; + return (peer.JniManagedPeerState & JniManagedPeerStates.Replaceable) == JniManagedPeerStates.Replaceable; + } + + void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepValue) + { + Runtime.ObjectReferenceManager.WriteGlobalReferenceLine ( + "Warning: Not registering PeerReference={0} IdentityHashCode=0x{1} Instance={2} Instance.Type={3} Java.Type={4}; " + + "keeping previously registered PeerReference={5} Instance={6} Instance.Type={7} Java.Type={8}.", + ignoreValue.PeerReference.ToString (), + key.ToString ("x"), + RuntimeHelpers.GetHashCode (ignoreValue).ToString ("x"), + ignoreValue.GetType ().FullName, + JniEnvironment.Types.GetJniTypeNameFromInstance (ignoreValue.PeerReference), + keepValue.PeerReference.ToString (), + RuntimeHelpers.GetHashCode (keepValue).ToString ("x"), + keepValue.GetType ().FullName, + JniEnvironment.Types.GetJniTypeNameFromInstance (keepValue.PeerReference)); + } + + public override IJavaPeerable? PeekPeer (JniObjectReference reference) + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (NativeAotValueManager)); + + if (!reference.IsValid) + return null; + + int key = GetJniIdentityHashCode (reference); + + lock (RegisteredInstances) { + List? peers; + if (!RegisteredInstances.TryGetValue (key, out peers)) + return null; + + for (int i = peers.Count - 1; i >= 0; i--) { + var p = peers [i]; + if (JniEnvironment.Types.IsSameObject (reference, p.PeerReference)) + return p; + } + if (peers.Count == 0) + RegisteredInstances.Remove (key); + } + return null; + } + + public override void RemovePeer (IJavaPeerable value) + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (NativeAotValueManager)); + + if (value == null) + throw new ArgumentNullException (nameof (value)); + + int key = value.JniIdentityHashCode; + lock (RegisteredInstances) { + List? peers; + if (!RegisteredInstances.TryGetValue (key, out peers)) + return; + + for (int i = peers.Count - 1; i >= 0; i--) { + var p = peers [i]; + if (object.ReferenceEquals (value, p)) { + peers.RemoveAt (i); + } + } + if (peers.Count == 0) + RegisteredInstances.Remove (key); + } + } + + public override void FinalizePeer (IJavaPeerable value) + { + var h = value.PeerReference; + var o = Runtime.ObjectReferenceManager; + // MUST NOT use SafeHandle.ReferenceType: local refs are tied to a JniEnvironment + // and the JniEnvironment's corresponding thread; it's a thread-local value. + // Accessing SafeHandle.ReferenceType won't kill anything (so far...), but + // instead it always returns JniReferenceType.Invalid. + if (!h.IsValid || h.Type == JniObjectReferenceType.Local) { + if (o.LogGlobalReferenceMessages) { + o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", + h.ToString (), + value.JniIdentityHashCode.ToString ("x"), + RuntimeHelpers.GetHashCode (value).ToString ("x"), + value.GetType ().ToString ()); + } + RemovePeer (value); + value.SetPeerReference (new JniObjectReference ()); + value.Finalized (); + return; + } + + RemovePeer (value); + if (o.LogGlobalReferenceMessages) { + o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", + h.ToString (), + value.JniIdentityHashCode.ToString ("x"), + RuntimeHelpers.GetHashCode (value).ToString ("x"), + value.GetType ().ToString ()); + } + value.SetPeerReference (new JniObjectReference ()); + JniObjectReference.Dispose (ref h); + value.Finalized (); + } + + public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) + { + try { + ActivateViaReflection (reference, cinfo, argumentValues); + } catch (Exception e) { + var m = string.Format ("Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.", + reference, + GetJniIdentityHashCode (reference).ToString ("x"), + JniEnvironment.Types.GetJniTypeNameFromInstance (reference), + cinfo.DeclaringType?.FullName); + Debug.WriteLine (m); + + throw new NotSupportedException (m, e); + } + } + + void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) + { + var declType = cinfo.DeclaringType ?? throw new NotSupportedException ("Do not know the type to create!"); + +#pragma warning disable IL2072 + var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType); +#pragma warning restore IL2072 + self.SetPeerReference (reference); + + cinfo.Invoke (self, argumentValues); + } + + public override List GetSurfacedPeers () + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (NativeAotValueManager)); + + lock (RegisteredInstances) { + var peers = new List (RegisteredInstances.Count); + foreach (var e in RegisteredInstances) { + foreach (var p in e.Value) { + peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference (p))); + } + } + return peers; + } + } + + public override IJavaPeerable? CreatePeer ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? targetType) + { + if (!reference.IsValid) + return null; + + var peer = CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer, targetType); + JniObjectReference.Dispose (ref reference, options); + return peer; + } + + internal IJavaPeerable? CreateInstance ( + IntPtr handle, + JniHandleOwnership transfer, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? targetType) + { + if (targetType.IsInterface || targetType.IsAbstract) { + var invokerType = JavaObjectExtensions.GetInvokerType (targetType); + if (invokerType == null) + throw new NotSupportedException ("Unable to find Invoker for type '" + targetType.FullName + "'. Was it linked away?", + CreateJavaLocationException ()); + targetType = invokerType; + } + + var typeSig = TypeManager.GetTypeSignature (targetType); + if (!typeSig.IsValid || typeSig.SimpleReference == null) { + throw new ArgumentException ($"Could not determine Java type corresponding to `{targetType.AssemblyQualifiedName}`.", nameof (targetType)); + } + + JniObjectReference typeClass = default; + JniObjectReference handleClass = default; + try { + try { + typeClass = JniEnvironment.Types.FindClass (typeSig.SimpleReference); + } catch (Exception e) { + throw new ArgumentException ($"Could not find Java class `{typeSig.SimpleReference}`.", + nameof (targetType), + e); + } + + handleClass = JniEnvironment.Types.GetObjectClass (new JniObjectReference (handle)); + if (!JniEnvironment.Types.IsAssignableFrom (handleClass, typeClass)) { + return null; + } + } finally { + JniObjectReference.Dispose (ref handleClass); + JniObjectReference.Dispose (ref typeClass); + } + + IJavaPeerable? result = null; + + try { + result = (IJavaPeerable) CreateProxy (targetType, handle, transfer); + //if (JNIEnv.IsGCUserPeer (result.PeerReference.Handle)) { + result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); + //} + } catch (MissingMethodException e) { + var key_handle = JNIEnv.IdentityHash (handle); + JNIEnv.DeleteRef (handle, transfer); + throw new NotSupportedException (FormattableString.Invariant ( + $"Unable to activate instance of type {targetType} from native handle 0x{handle:x} (key_handle 0x{key_handle:x})."), e); + } + return result; + } + + static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; + static readonly Type[] JIConstructorSignature = new Type [] { typeof (JniObjectReference).MakeByRefType (), typeof (JniObjectReferenceOptions) }; + + internal static object CreateProxy ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type, + IntPtr handle, + JniHandleOwnership transfer) + { + // Skip Activator.CreateInstance() as that requires public constructors, + // and we want to hide some constructors for sanity reasons. + BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var c = type.GetConstructor (flags, null, XAConstructorSignature, null); + if (c != null) { + return c.Invoke (new object [] { handle, transfer }); + } + c = type.GetConstructor (flags, null, JIConstructorSignature, null); + if (c != null) { + JniObjectReference r = new JniObjectReference (handle); + JniObjectReferenceOptions o = JniObjectReferenceOptions.Copy; + var peer = (IJavaPeerable) c.Invoke (new object [] { r, o }); + JNIEnv.DeleteRef (handle, transfer); + return peer; + } + throw new MissingMethodException ( + "No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)", + CreateJavaLocationException ()); + } + + static Exception CreateJavaLocationException () + { + using (var loc = new Java.Lang.Error ("Java callstack:")) + return new JavaLocationException (loc.ToString ()); + } } From d54546a941cd8be5f9dc16a349591ab9e32a8907 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 31 Jan 2025 10:26:47 -0600 Subject: [PATCH 11/15] Use `Name` property instead of `[Register]` --- samples/NativeAOT/MainActivity.cs | 4 ++-- samples/NativeAOT/MainApplication.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/NativeAOT/MainActivity.cs b/samples/NativeAOT/MainActivity.cs index 5fdcfd24922..aaedc0aef2d 100644 --- a/samples/NativeAOT/MainActivity.cs +++ b/samples/NativeAOT/MainActivity.cs @@ -5,8 +5,8 @@ namespace NativeAOT; -[Register("my/MainActivity")] // Required for typemap in NativeAotTypeManager -[Activity(Label = "@string/app_name", MainLauncher = true)] +// Name required for typemap in NativeAotTypeManager +[Activity (Label = "@string/app_name", MainLauncher = true, Name = "my.MainActivity")] public class MainActivity : Activity { protected override void OnCreate(Bundle? savedInstanceState) diff --git a/samples/NativeAOT/MainApplication.cs b/samples/NativeAOT/MainApplication.cs index 9bfe73a23b7..103622cf4fc 100644 --- a/samples/NativeAOT/MainApplication.cs +++ b/samples/NativeAOT/MainApplication.cs @@ -3,9 +3,9 @@ /// /// NOTE: This class is not required, but used for testing Android.App.Application subclasses. +/// Name required for typemap in NativeAotTypeManager /// -[Register ("my/MainApplication")] // Required for typemap in NativeAotTypeManager -[Application] +[Application (Name = "my.MainApplication")] public class MainApplication : Application { public MainApplication (IntPtr handle, JniHandleOwnership transfer) From 694e975ec4ef2fbe32cde842fcc38f8e7583261f Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 31 Jan 2025 14:51:48 -0500 Subject: [PATCH 12/15] Fix Application subclass usage. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://discord.com/channels/732297728826277939/732297837953679412/1334614545871929345 PR #9716 was crashing with a stack overflow: I NativeAotFromAndroid: at Java.Interop.JniEnvironment.InstanceMethods.CallVoidMethod(JniObjectReference, JniMethodInfo, JniArgumentValue*) + 0xa8 I NativeAotFromAndroid: at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualVoidMethod(String, IJavaPeerable, JniArgumentValue*) + 0x184 I NativeAotFromAndroid: at Android.App.Application.n_OnCreate(IntPtr jnienv, IntPtr native__this) + 0xa8 I NativeAotFromAndroid: at libNativeAOT!+0x4f3e44 The cause was the topmost frame: `CallVoidMethod()`, which performs a *virtual* method invocation. The stack overflow was that Java `MainApplication.onCreate()` called C# `Application.n_OnCreate()`, which called `InvokeVirtualVoidMethod()`, which did a *virtual* invocation back on `MainApplication.onCreate()`, … `InvokeVirtualVoidMethod()` should have been calling `CallNonvirtualVoidMethod()`; why wasn't it? Further investigation showed: Created PeerReference=0x2d06/G IdentityHashCode=0x8edcb07 Instance=0x957d2a Instance.Type=Android.App.Application, Java.Type=my/MainApplication which at a glance seems correct, but isn't: the `Instance.Type` for a `Java.Type` of `my/MainApplication` should be `MainApplication`, *not* `Android.App.Application`! Because the runtime type of this value was `Application`, it was warranted and expected that `InvokeVirtualVoidMethod()` would do a virtual invocation! So, why did the avove `Created PeerReference …` line show the wrong type? Because `NativeAotTypeManager.CreatePeer()` needs to check for bindings of the the runtime type of the Java handle *before* using the `targetType` parameter, because the targetType parameter will *never* be for that of a custom subclass. Copy *lots* of code from dotnet/java-interop -- showing that this needs some major cleanup & refactoring -- so that we properly check the runtime type of `reference` + base classes when trying to determine the type of the proxy to create. This fixes the stack overflow. --- samples/NativeAOT/MainApplication.cs | 1 + samples/NativeAOT/NativeAotValueManager.cs | 223 ++++++++++++++------- 2 files changed, 151 insertions(+), 73 deletions(-) diff --git a/samples/NativeAOT/MainApplication.cs b/samples/NativeAOT/MainApplication.cs index 103622cf4fc..e3f2c6f546f 100644 --- a/samples/NativeAOT/MainApplication.cs +++ b/samples/NativeAOT/MainApplication.cs @@ -11,6 +11,7 @@ public class MainApplication : Application public MainApplication (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { + Log.Debug ("NativeAOT", $"Application..ctor({handle.ToString ("x2")}, {transfer})"); } public override void OnCreate () diff --git a/samples/NativeAOT/NativeAotValueManager.cs b/samples/NativeAOT/NativeAotValueManager.cs index 760de0a74fd..0b201e58c1e 100644 --- a/samples/NativeAOT/NativeAotValueManager.cs +++ b/samples/NativeAOT/NativeAotValueManager.cs @@ -17,6 +17,8 @@ namespace NativeAOT; internal class NativeAotValueManager : JniRuntime.JniValueManager { + const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; + readonly NativeAotTypeManager TypeManager; Dictionary>? RegisteredInstances = new Dictionary>(); @@ -253,105 +255,180 @@ public override List GetSurfacedPeers () public override IJavaPeerable? CreatePeer ( ref JniObjectReference reference, - JniObjectReferenceOptions options, - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + JniObjectReferenceOptions transfer, + [DynamicallyAccessedMembers (Constructors)] Type? targetType) { - if (!reference.IsValid) + if (!reference.IsValid) { return null; + } - var peer = CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer, targetType); - JniObjectReference.Dispose (ref reference, options); - return peer; - } + targetType = targetType ?? typeof (global::Java.Interop.JavaObject); + targetType = GetPeerType (targetType); - internal IJavaPeerable? CreateInstance ( - IntPtr handle, - JniHandleOwnership transfer, - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type? targetType) - { - if (targetType.IsInterface || targetType.IsAbstract) { - var invokerType = JavaObjectExtensions.GetInvokerType (targetType); - if (invokerType == null) - throw new NotSupportedException ("Unable to find Invoker for type '" + targetType.FullName + "'. Was it linked away?", - CreateJavaLocationException ()); - targetType = invokerType; - } + if (!typeof (IJavaPeerable).IsAssignableFrom (targetType)) + throw new ArgumentException ($"targetType `{targetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType)); - var typeSig = TypeManager.GetTypeSignature (targetType); - if (!typeSig.IsValid || typeSig.SimpleReference == null) { + var targetSig = Runtime.TypeManager.GetTypeSignature (targetType); + if (!targetSig.IsValid || targetSig.SimpleReference == null) { throw new ArgumentException ($"Could not determine Java type corresponding to `{targetType.AssemblyQualifiedName}`.", nameof (targetType)); } - JniObjectReference typeClass = default; - JniObjectReference handleClass = default; + var refClass = JniEnvironment.Types.GetObjectClass (reference); + JniObjectReference targetClass; try { - try { - typeClass = JniEnvironment.Types.FindClass (typeSig.SimpleReference); - } catch (Exception e) { - throw new ArgumentException ($"Could not find Java class `{typeSig.SimpleReference}`.", - nameof (targetType), - e); - } + targetClass = JniEnvironment.Types.FindClass (targetSig.SimpleReference); + } catch (Exception e) { + JniObjectReference.Dispose (ref refClass); + throw new ArgumentException ($"Could not find Java class `{targetSig.SimpleReference}`.", + nameof (targetType), + e); + } + + if (!JniEnvironment.Types.IsAssignableFrom (refClass, targetClass)) { + JniObjectReference.Dispose (ref refClass); + JniObjectReference.Dispose (ref targetClass); + return null; + } + + JniObjectReference.Dispose (ref targetClass); + + var proxy = CreatePeerProxy (ref refClass, targetType, ref reference, transfer); + + if (proxy == null) { + throw new NotSupportedException (string.Format ("Could not find an appropriate constructable wrapper type for Java type '{0}', targetType='{1}'.", + JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType)); + } + + proxy.SetJniManagedPeerState (proxy.JniManagedPeerState | JniManagedPeerStates.Replaceable); + return proxy; + } + + [return: DynamicallyAccessedMembers (Constructors)] + static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) + { + if (type == typeof (object)) + return typeof (global::Java.Interop.JavaObject); + if (type == typeof (IJavaPeerable)) + return typeof (global::Java.Interop.JavaObject); + if (type == typeof (Exception)) + return typeof (global::Java.Interop.JavaException); + return type; + } + + static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType (); + + IJavaPeerable? CreatePeerProxy ( + ref JniObjectReference klass, + [DynamicallyAccessedMembers (Constructors)] + Type fallbackType, + ref JniObjectReference reference, + JniObjectReferenceOptions options) + { + var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass); - handleClass = JniEnvironment.Types.GetObjectClass (new JniObjectReference (handle)); - if (!JniEnvironment.Types.IsAssignableFrom (handleClass, typeClass)) { + Type? type = null; + while (jniTypeName != null) { + JniTypeSignature sig; + if (!JniTypeSignature.TryParse (jniTypeName, out sig)) return null; + + type = Runtime.TypeManager.GetType (sig); + + if (type != null) { + var peer = TryCreatePeerProxy (type, ref reference, options); + if (peer != null) { + return peer; + } } - } finally { - JniObjectReference.Dispose (ref handleClass); - JniObjectReference.Dispose (ref typeClass); + + var super = JniEnvironment.Types.GetSuperclass (klass); + jniTypeName = super.IsValid + ? JniEnvironment.Types.GetJniTypeNameFromClass (super) + : null; + + JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose); + klass = super; } + JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose); - IJavaPeerable? result = null; + return TryCreatePeerProxy (fallbackType, ref reference, options); + } - try { - result = (IJavaPeerable) CreateProxy (targetType, handle, transfer); - //if (JNIEnv.IsGCUserPeer (result.PeerReference.Handle)) { - result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); - //} - } catch (MissingMethodException e) { - var key_handle = JNIEnv.IdentityHash (handle); - JNIEnv.DeleteRef (handle, transfer); - throw new NotSupportedException (FormattableString.Invariant ( - $"Unable to activate instance of type {targetType} from native handle 0x{handle:x} (key_handle 0x{key_handle:x})."), e); + static ConstructorInfo? GetActivationConstructor ( + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + if (type.IsAbstract || type.IsInterface) { + type = GetInvokerType (type) ?? type; } - return result; + foreach (var c in type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { + var p = c.GetParameters (); + if (p.Length == 2 && p [0].ParameterType == ByRefJniObjectReference && p [1].ParameterType == typeof (JniObjectReferenceOptions)) + return c; + if (p.Length == 2 && p [0].ParameterType == typeof (IntPtr) && p [1].ParameterType == typeof (JniHandleOwnership)) + return c; + } + return null; } + [return: DynamicallyAccessedMembers (Constructors)] + static Type? GetInvokerType (Type type) + { + // https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186 + const string makeGenericTypeMessage = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step."; + + [UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)] + [return: DynamicallyAccessedMembers (Constructors)] + static Type MakeGenericType ( + [DynamicallyAccessedMembers (Constructors)] + Type type, + Type [] arguments) => + // FIXME: https://github.com/dotnet/java-interop/issues/1192 + #pragma warning disable IL3050 + type.MakeGenericType (arguments); + #pragma warning restore IL3050 + + var signature = type.GetCustomAttribute (); + if (signature == null || signature.InvokerType == null) { + return null; + } + + Type[] arguments = type.GetGenericArguments (); + if (arguments.Length == 0) + return signature.InvokerType; + + return MakeGenericType (signature.InvokerType, arguments); + } + + const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + + static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; static readonly Type[] JIConstructorSignature = new Type [] { typeof (JniObjectReference).MakeByRefType (), typeof (JniObjectReferenceOptions) }; - internal static object CreateProxy ( - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type type, - IntPtr handle, - JniHandleOwnership transfer) + protected virtual IJavaPeerable? TryCreatePeerProxy (Type type, ref JniObjectReference reference, JniObjectReferenceOptions options) { - // Skip Activator.CreateInstance() as that requires public constructors, - // and we want to hide some constructors for sanity reasons. - BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - var c = type.GetConstructor (flags, null, XAConstructorSignature, null); + var c = type.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null); if (c != null) { - return c.Invoke (new object [] { handle, transfer }); + var args = new object[] { + reference.Handle, + JniHandleOwnership.DoNotTransfer, + }; + var p = (IJavaPeerable) c.Invoke (args); + JniObjectReference.Dispose (ref reference, options); + return p; } - c = type.GetConstructor (flags, null, JIConstructorSignature, null); + c = type.GetConstructor (ActivationConstructorBindingFlags, null, JIConstructorSignature, null); if (c != null) { - JniObjectReference r = new JniObjectReference (handle); - JniObjectReferenceOptions o = JniObjectReferenceOptions.Copy; - var peer = (IJavaPeerable) c.Invoke (new object [] { r, o }); - JNIEnv.DeleteRef (handle, transfer); - return peer; + var args = new object[] { + reference, + options, + }; + var p = (IJavaPeerable) c.Invoke (args); + reference = (JniObjectReference) args [0]; + return p; } - throw new MissingMethodException ( - "No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)", - CreateJavaLocationException ()); - } - - static Exception CreateJavaLocationException () - { - using (var loc = new Java.Lang.Error ("Java callstack:")) - return new JavaLocationException (loc.ToString ()); + return null; } } From 87088f4df82471835b61ad0e18f5c9ce27cd902f Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 31 Jan 2025 15:48:37 -0600 Subject: [PATCH 13/15] Unused code --- samples/NativeAOT/NativeAotValueManager.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/samples/NativeAOT/NativeAotValueManager.cs b/samples/NativeAOT/NativeAotValueManager.cs index 0b201e58c1e..0f8fcbc3a02 100644 --- a/samples/NativeAOT/NativeAotValueManager.cs +++ b/samples/NativeAOT/NativeAotValueManager.cs @@ -316,8 +316,6 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) return type; } - static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType (); - IJavaPeerable? CreatePeerProxy ( ref JniObjectReference klass, [DynamicallyAccessedMembers (Constructors)] @@ -355,23 +353,6 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) return TryCreatePeerProxy (fallbackType, ref reference, options); } - static ConstructorInfo? GetActivationConstructor ( - [DynamicallyAccessedMembers (Constructors)] - Type type) - { - if (type.IsAbstract || type.IsInterface) { - type = GetInvokerType (type) ?? type; - } - foreach (var c in type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { - var p = c.GetParameters (); - if (p.Length == 2 && p [0].ParameterType == ByRefJniObjectReference && p [1].ParameterType == typeof (JniObjectReferenceOptions)) - return c; - if (p.Length == 2 && p [0].ParameterType == typeof (IntPtr) && p [1].ParameterType == typeof (JniHandleOwnership)) - return c; - } - return null; - } - [return: DynamicallyAccessedMembers (Constructors)] static Type? GetInvokerType (Type type) { From c19341f10d59b14624d2cecc0ba0a9d0f575ff92 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 31 Jan 2025 15:55:45 -0600 Subject: [PATCH 14/15] Use HasFlag --- samples/NativeAOT/NativeAotValueManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/NativeAOT/NativeAotValueManager.cs b/samples/NativeAOT/NativeAotValueManager.cs index 0f8fcbc3a02..f1914395ffe 100644 --- a/samples/NativeAOT/NativeAotValueManager.cs +++ b/samples/NativeAOT/NativeAotValueManager.cs @@ -104,7 +104,7 @@ static bool Replaceable (IJavaPeerable peer) { if (peer == null) return true; - return (peer.JniManagedPeerState & JniManagedPeerStates.Replaceable) == JniManagedPeerStates.Replaceable; + return peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable); } void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepValue) From 6b2ab27c07eb19ea6caa308757a7fc8059e12bcf Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 31 Jan 2025 15:57:20 -0600 Subject: [PATCH 15/15] JniRuntime.JniTypeManager to be less restrictive --- samples/NativeAOT/NativeAotValueManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/NativeAOT/NativeAotValueManager.cs b/samples/NativeAOT/NativeAotValueManager.cs index f1914395ffe..e233af7a480 100644 --- a/samples/NativeAOT/NativeAotValueManager.cs +++ b/samples/NativeAOT/NativeAotValueManager.cs @@ -19,10 +19,10 @@ internal class NativeAotValueManager : JniRuntime.JniValueManager { const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; - readonly NativeAotTypeManager TypeManager; + readonly JniRuntime.JniTypeManager TypeManager; Dictionary>? RegisteredInstances = new Dictionary>(); - public NativeAotValueManager(NativeAotTypeManager typeManager) => + public NativeAotValueManager(JniRuntime.JniTypeManager typeManager) => TypeManager = typeManager; public override void WaitForGCBridgeProcessing ()