From 203ce44ee387703d7b1bde5d75ae43c1447bf430 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 19 Feb 2025 10:24:18 -0600 Subject: [PATCH] [Mono.Android] add fallback for `TypemapManagedToJava` The .NET MAUI template + NativeAOT currently crashes with: 02-18 15:59:24.575 12907 12907 E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidProgramException: InvalidProgram_Specific, IntPtr Android.Runtime.JNIEnv.monodroid_typemap_managed_to_java(System.Type, Byte*) 02-18 15:59:24.575 12907 12907 E AndroidRuntime: at Internal.Runtime.TypeLoaderExceptionHelper.CreateInvalidProgramException(ExceptionStringID, String) + 0x4c 02-18 15:59:24.575 12907 12907 E AndroidRuntime: at Android.Runtime.JNIEnv.monodroid_typemap_managed_to_java(Type, Byte*) + 0x18 02-18 15:59:24.575 12907 12907 E AndroidRuntime: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type) + 0x104 02-18 15:59:24.575 12907 12907 E AndroidRuntime: at Android.Runtime.JNIEnv.GetJniName(Type) + 0x1c 02-18 15:59:24.575 12907 12907 E AndroidRuntime: at Android.Runtime.JNIEnv.FindClass(Type) + 0x38 02-18 15:59:24.575 12907 12907 E AndroidRuntime: at Android.Runtime.JNIEnv.NewArray(IJavaObject[]) + 0x28 02-18 15:59:24.575 12907 12907 E AndroidRuntime: at Android.Runtime.JNIEnv.NewArray[T](T[]) + 0x94 02-18 15:59:24.575 12907 12907 E AndroidRuntime: at Android.Graphics.Drawables.LayerDrawable..ctor(Drawable[] layers) + 0xd4 02-18 15:59:24.575 12907 12907 E AndroidRuntime: at Microsoft.Maui.Platform.MauiRippleDrawableExtensions.UpdateMauiRippleDrawableBackground(View, Paint, IButtonStroke, Func`1, Func`1, Action) + 0x2ac This appears to be related to array usage, such as `LayerDrawable.ctor(Drawable[])` in this example. I can reproduce the same crash using a `ColorStateList.ctor(int[][], int[])` in our NativeAOT "hello world" sample: 02-19 10:45:29.728 28692 28692 E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidProgramException: InvalidProgram_Specific, IntPtr Android.Runtime.JNIEnv.monodroid_typemap_managed_to_java(System.Type, Byte*) 02-19 10:45:29.728 28692 28692 E AndroidRuntime: at Internal.Runtime.TypeLoaderExceptionHelper.CreateInvalidProgramException(ExceptionStringID, String) + 0x4c 02-19 10:45:29.728 28692 28692 E AndroidRuntime: at Android.Runtime.JNIEnv.monodroid_typemap_managed_to_java(Type, Byte*) + 0x18 02-19 10:45:29.728 28692 28692 E AndroidRuntime: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type) + 0x104 02-19 10:45:29.728 28692 28692 E AndroidRuntime: at Android.Runtime.JNIEnv.GetJniName(Type) + 0x1c 02-19 10:45:29.728 28692 28692 E AndroidRuntime: at Android.Runtime.JNIEnv.FindClass(Type) + 0x38 02-19 10:45:29.728 28692 28692 E AndroidRuntime: at Android.Runtime.JNIEnv.NewArray[T](T[]) + 0xa8 02-19 10:45:29.728 28692 28692 E AndroidRuntime: at Android.Content.Res.ColorStateList..ctor(Int32[][], Int32[]) + 0xdc 02-19 10:45:29.728 28692 28692 E AndroidRuntime: at NativeAOT.MainActivity.OnCreate(Bundle savedInstanceState) + 0xb8 To fix this: * Introduce a new `Android.Runtime.RuntimeFeature.UseReflectionForManagedToJava` feature switch * If the switch is toggled, call `JavaNativeTypeManager.ToJniName(System.Type)` instead of `TypemapManagedToJava()`. * Toggle the feature switch for NativeAOT. In the future, we may enable this switch as a stopgap for getting other runtimes to launch successfully. To reduce confusion, I also removed the managed side of `JNIEnvInit.IsRunningOnDesktop`, which was support for the Android designer. It was using the exact same fallback after attempting `TypemapManagedToJava()`. In a future PR, we could probably also remove the native C/C++ side of this flag. --- samples/NativeAOT/MainActivity.cs | 5 +++++ src/Mono.Android/Android.App/SyncContext.cs | 2 -- .../Android.Runtime/AndroidRuntime.cs | 16 +++++++++------- src/Mono.Android/Android.Runtime/JNIEnv.cs | 3 +++ src/Mono.Android/Android.Runtime/JNIEnvInit.cs | 11 ----------- .../Android.Runtime/RuntimeFeature.cs | 13 +++++++++++++ src/Mono.Android/ILLink/ILLink.Substitutions.xml | 4 ++++ src/Mono.Android/Java.Interop/TypeManager.cs | 12 +++++------- src/Mono.Android/Mono.Android.csproj | 1 + .../Microsoft.Android.Sdk.NativeAOT.targets | 1 + .../Microsoft.Android.Sdk.RuntimeConfig.targets | 4 ++++ .../mono/monodroid/monodroid-glue-internal.hh | 1 - src/native/mono/monodroid/monodroid-glue.cc | 1 - 13 files changed, 45 insertions(+), 29 deletions(-) create mode 100644 src/Mono.Android/Android.Runtime/RuntimeFeature.cs diff --git a/samples/NativeAOT/MainActivity.cs b/samples/NativeAOT/MainActivity.cs index aaedc0aef2d..3550139e1b5 100644 --- a/samples/NativeAOT/MainActivity.cs +++ b/samples/NativeAOT/MainActivity.cs @@ -1,3 +1,4 @@ +using Android.Content.Res; using Android.Runtime; using Android.Util; using System.Reflection; @@ -17,5 +18,9 @@ protected override void OnCreate(Bundle? savedInstanceState) // Set our view from the "main" layout resource SetContentView(Resource.Layout.activity_main); + + // An example of an Android API that uses a Java array + var list = new ColorStateList (new int[][] { [ 0, 1 ]}, [0, 1]); + Log.Debug ("NativeAOT", "MainActivity.OnCreate() ColorStateList: " + list); } } \ No newline at end of file diff --git a/src/Mono.Android/Android.App/SyncContext.cs b/src/Mono.Android/Android.App/SyncContext.cs index 6c3daa83c7d..4d15f773606 100644 --- a/src/Mono.Android/Android.App/SyncContext.cs +++ b/src/Mono.Android/Android.App/SyncContext.cs @@ -18,8 +18,6 @@ static bool EnsureLooper ([NotNullWhen (true)]Looper? looper, SendOrPostCallback { if (looper == null) { var message = $"No Android message loop is available. Skipping invocation of `{d.Method.DeclaringType?.FullName}.{d.Method.Name}`!"; - if (JNIEnvInit.IsRunningOnDesktop) - message += " Using `await` when running on the Android Designer is not currently supported. Please use the `View.IsInEditMode` property."; Logger.Log (LogLevel.Error, "monodroid-synccontext", message); return false; } diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index abf30a6ef4b..2abf1d9247a 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -278,23 +278,22 @@ protected override IEnumerable GetTypesForSimpleReference (string jniSimpl protected override string? GetSimpleReference (Type type) { + if (RuntimeFeature.UseReflectionForManagedToJava) { + return JavaNativeTypeManager.ToJniName (type); + } string? j = JNIEnv.TypemapManagedToJava (type); if (j != null) { return GetReplacementTypeCore (j) ?? j; } - if (JNIEnvInit.IsRunningOnDesktop) { - return JavaNativeTypeManager.ToJniName (type); - } return null; } protected override IEnumerable GetSimpleReferences (Type type) { - string? j = JNIEnv.TypemapManagedToJava (type); - j = GetReplacementTypeCore (j) ?? j; - - if (JNIEnvInit.IsRunningOnDesktop) { + string? j; + if (RuntimeFeature.UseReflectionForManagedToJava) { string? d = JavaNativeTypeManager.ToJniName (type); + j = GetReplacementTypeCore (d); if (j != null && d != null) { return new[]{j, d}; } @@ -302,6 +301,9 @@ protected override IEnumerable GetSimpleReferences (Type type) return new[]{d}; } } + + j = JNIEnv.TypemapManagedToJava (type); + j = GetReplacementTypeCore (j) ?? j; if (j != null) { return new[]{j}; } diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 9cf5207f6af..afb6c4e7dc9 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -446,6 +446,9 @@ public static string GetJniName (Type type) if (type == null) throw new ArgumentNullException ("type"); + if (RuntimeFeature.UseReflectionForManagedToJava) + return JavaNativeTypeManager.ToJniName (type); + string? java = TypemapManagedToJava (type); return java == null ? JavaNativeTypeManager.ToJniName (type) diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 2499b693cf2..504e7ade4f5 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -24,7 +24,6 @@ internal struct JnienvInitializeArgs { public int version; // TODO: remove, not needed anymore public int grefGcThreshold; public IntPtr grefIGCUserPeer; - public int isRunningOnDesktop; public byte brokenExceptionTransitions; public int packageNamingPolicy; public byte ioExceptionType; @@ -36,7 +35,6 @@ internal struct JnienvInitializeArgs { #pragma warning restore 0649 internal static JniRuntime.JniValueManager? ValueManager; - internal static bool IsRunningOnDesktop; internal static bool jniRemappingInUse; internal static bool MarshalMethodsEnabled; internal static bool PropagateExceptions; @@ -102,21 +100,12 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) androidRuntime = new AndroidRuntime (args->env, args->javaVm, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); ValueManager = androidRuntime.ValueManager; - IsRunningOnDesktop = args->isRunningOnDesktop == 1; - grefIGCUserPeer_class = args->grefIGCUserPeer; grefGCUserPeerable_class = args->grefGCUserPeerable; PropagateExceptions = args->brokenExceptionTransitions == 0; JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy; - if (IsRunningOnDesktop) { - var packageNamingPolicy = Environment.GetEnvironmentVariable ("__XA_PACKAGE_NAMING_POLICY__"); - if (Enum.TryParse (packageNamingPolicy, out PackageNamingPolicy pnp)) { - JavaNativeTypeManager.PackageNamingPolicy = pnp; - } - } - SetSynchronizationContext (); } diff --git a/src/Mono.Android/Android.Runtime/RuntimeFeature.cs b/src/Mono.Android/Android.Runtime/RuntimeFeature.cs new file mode 100644 index 00000000000..a62d23310ec --- /dev/null +++ b/src/Mono.Android/Android.Runtime/RuntimeFeature.cs @@ -0,0 +1,13 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Android.Runtime; + +static class RuntimeFeature +{ + const string FeatureSwitchPrefix = "Android.Runtime.RuntimeFeature."; + + [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (UseReflectionForManagedToJava)}")] + internal static bool UseReflectionForManagedToJava { get; } = + AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (UseReflectionForManagedToJava)}", out bool isEnabled) ? isEnabled : false; +} diff --git a/src/Mono.Android/ILLink/ILLink.Substitutions.xml b/src/Mono.Android/ILLink/ILLink.Substitutions.xml index 80459d1cd60..f1617bad66f 100644 --- a/src/Mono.Android/ILLink/ILLink.Substitutions.xml +++ b/src/Mono.Android/ILLink/ILLink.Substitutions.xml @@ -4,5 +4,9 @@ + + + + diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 8b775aea7db..b7839867a1d 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -221,12 +221,10 @@ static Exception CreateJavaLocationException () if (type != null) return type; - if (!JNIEnvInit.IsRunningOnDesktop) { - // Miss message is logged in the native runtime - if (Logger.LogAssembly) - JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true)); - return null; - } + // Miss message is logged in the native runtime + if (Logger.LogAssembly) + JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true)); + return null; return null; } @@ -368,7 +366,7 @@ public static void RegisterType (string java_class, Type t) if (String.Compare (jniFromType, java_class, StringComparison.OrdinalIgnoreCase) != 0) { TypeManagerMapDictionaries.ManagedToJni.Add (t, java_class); } - } else if (!JNIEnvInit.IsRunningOnDesktop || t != typeof (Java.Interop.TypeManager)) { + } else if (t != typeof (Java.Interop.TypeManager)) { // skip the registration and output a warning Logger.Log (LogLevel.Warn, "monodroid", FormattableString.Invariant ($"Type Registration Skipped for {java_class} to {t} ")); } diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 6c409057f53..9aea5e4840c 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -258,6 +258,7 @@ + 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 0735371eacc..473c9399a54 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 @@ -13,6 +13,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. <_AndroidRuntimePackRuntime>NativeAOT JavaInterop1 + <_AndroidUseReflectionForManagedToJava Condition=" '$(_AndroidUseReflectionForManagedToJava)' == '' ">true true diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets index 899156a5b4e..506b343dbb1 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets @@ -46,6 +46,10 @@ See: https://github.com/dotnet/runtime/blob/b13715b6984889a709ba29ea8a1961db469f Value="$(AndroidAvoidEmitForPerformance)" Trim="true" /> + GetVersion (); - init.isRunningOnDesktop = is_running_on_desktop ? 1 : 0; init.brokenExceptionTransitions = application_config.broken_exception_transitions ? 1 : 0; init.packageNamingPolicy = static_cast(application_config.package_naming_policy); init.boundExceptionType = application_config.bound_exception_type;