Skip to content

Commit

Permalink
[Mono.Android] add fallback for TypemapManagedToJava
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jonathanpeppers committed Feb 19, 2025
1 parent 66ffd38 commit 203ce44
Show file tree
Hide file tree
Showing 13 changed files with 45 additions and 29 deletions.
5 changes: 5 additions & 0 deletions samples/NativeAOT/MainActivity.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Android.Content.Res;
using Android.Runtime;
using Android.Util;
using System.Reflection;
Expand All @@ -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);
}
}
2 changes: 0 additions & 2 deletions src/Mono.Android/Android.App/SyncContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
16 changes: 9 additions & 7 deletions src/Mono.Android/Android.Runtime/AndroidRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,30 +278,32 @@ protected override IEnumerable<Type> 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<string> 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};
}
if (d != null) {
return new[]{d};
}
}

j = JNIEnv.TypemapManagedToJava (type);
j = GetReplacementTypeCore (j) ?? j;
if (j != null) {
return new[]{j};
}
Expand Down
3 changes: 3 additions & 0 deletions src/Mono.Android/Android.Runtime/JNIEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 0 additions & 11 deletions src/Mono.Android/Android.Runtime/JNIEnvInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 ();
}

Expand Down
13 changes: 13 additions & 0 deletions src/Mono.Android/Android.Runtime/RuntimeFeature.cs
Original file line number Diff line number Diff line change
@@ -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;
}
4 changes: 4 additions & 0 deletions src/Mono.Android/ILLink/ILLink.Substitutions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
<method signature="System.Boolean get_NegotiateAuthenticationIsEnabled()" body="stub" feature="Xamarin.Android.Net.UseNegotiateAuthentication" featurevalue="false" value="false" />
<method signature="System.Boolean get_NegotiateAuthenticationIsEnabled()" body="stub" feature="Xamarin.Android.Net.UseNegotiateAuthentication" featurevalue="true" value="true" />
</type>
<type fullname="Android.Runtime.RuntimeFeature">
<method signature="System.Boolean get_UseReflectionForManagedToJava()" body="stub" feature="Android.Runtime.RuntimeFeature.UseReflectionForManagedToJava" featurevalue="false" value="false" />
<method signature="System.Boolean get_UseReflectionForManagedToJava()" body="stub" feature="Android.Runtime.RuntimeFeature.UseReflectionForManagedToJava" featurevalue="true" value="true" />
</type>
</assembly>
</linker>
12 changes: 5 additions & 7 deletions src/Mono.Android/Java.Interop/TypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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} "));
}
Expand Down
1 change: 1 addition & 0 deletions src/Mono.Android/Mono.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@
<Compile Include="Android.Runtime\RequiresPermissionAttribute.cs" />
<Compile Include="Android.Runtime\ResourceDesignerAttribute.cs" />
<Compile Include="Android.Runtime\RuntimeConstants.cs" />
<Compile Include="Android.Runtime\RuntimeFeature.cs" />
<Compile Include="Android.Runtime\ResourceIdManager.cs" />
<Compile Include="Android.Runtime\RuntimeNativeMethods.cs" />
<Compile Include="Android.Runtime\StringDefAttribute.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
<PropertyGroup>
<_AndroidRuntimePackRuntime>NativeAOT</_AndroidRuntimePackRuntime>
<AndroidCodegenTarget Condition=" '$(AndroidCodegenTarget)' == '' ">JavaInterop1</AndroidCodegenTarget>
<_AndroidUseReflectionForManagedToJava Condition=" '$(_AndroidUseReflectionForManagedToJava)' == '' ">true</_AndroidUseReflectionForManagedToJava>
<!-- NativeAOT's targets currently gives an error about cross-compilation -->
<DisableUnsupportedError Condition=" $([MSBuild]::IsOSPlatform('windows')) and '$(DisableUnsupportedError)' == '' ">true</DisableUnsupportedError>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ See: https://github.com/dotnet/runtime/blob/b13715b6984889a709ba29ea8a1961db469f
Value="$(AndroidAvoidEmitForPerformance)"
Trim="true"
/>
<RuntimeHostConfigurationOption Include="Android.Runtime.RuntimeFeature.UseReflectionForManagedToJava"
Value="$([MSBuild]::ValueOrDefault('$(_AndroidUseReflectionForManagedToJava)', 'false'))"
Trim="true"
/>
</ItemGroup>

<Target Name="_ParseRuntimeConfigFiles"
Expand Down
1 change: 0 additions & 1 deletion src/native/mono/monodroid/monodroid-glue-internal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ namespace xamarin::android::internal
int version;
int grefGcThreshold;
jobject grefIGCUserPeer;
int isRunningOnDesktop;
uint8_t brokenExceptionTransitions;
int packageNamingPolicy;
uint8_t boundExceptionType;
Expand Down
1 change: 0 additions & 1 deletion src/native/mono/monodroid/monodroid-glue.cc
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,6 @@ MonodroidRuntime::init_android_runtime (JNIEnv *env, jclass runtimeClass, jobjec
init.env = env;
init.logCategories = log_categories;
init.version = env->GetVersion ();
init.isRunningOnDesktop = is_running_on_desktop ? 1 : 0;
init.brokenExceptionTransitions = application_config.broken_exception_transitions ? 1 : 0;
init.packageNamingPolicy = static_cast<int>(application_config.package_naming_policy);
init.boundExceptionType = application_config.bound_exception_type;
Expand Down

0 comments on commit 203ce44

Please sign in to comment.