Skip to content

Commit 8c14733

Browse files
[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.
1 parent 66ffd38 commit 8c14733

File tree

13 files changed

+45
-29
lines changed

13 files changed

+45
-29
lines changed

samples/NativeAOT/MainActivity.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Android.Content.Res;
12
using Android.Runtime;
23
using Android.Util;
34
using System.Reflection;
@@ -17,5 +18,9 @@ protected override void OnCreate(Bundle? savedInstanceState)
1718

1819
// Set our view from the "main" layout resource
1920
SetContentView(Resource.Layout.activity_main);
21+
22+
// An example of an Android API that uses a Java array
23+
var list = new ColorStateList (new int[][] { [ 0, 1 ]}, [0, 1]);
24+
Log.Debug ("NativeAOT", "MainActivity.OnCreate() ColorStateList: " + list);
2025
}
2126
}

src/Mono.Android/Android.App/SyncContext.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ static bool EnsureLooper ([NotNullWhen (true)]Looper? looper, SendOrPostCallback
1818
{
1919
if (looper == null) {
2020
var message = $"No Android message loop is available. Skipping invocation of `{d.Method.DeclaringType?.FullName}.{d.Method.Name}`!";
21-
if (JNIEnvInit.IsRunningOnDesktop)
22-
message += " Using `await` when running on the Android Designer is not currently supported. Please use the `View.IsInEditMode` property.";
2321
Logger.Log (LogLevel.Error, "monodroid-synccontext", message);
2422
return false;
2523
}

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -278,30 +278,32 @@ protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpl
278278

279279
protected override string? GetSimpleReference (Type type)
280280
{
281+
if (RuntimeFeature.UseReflectionForManagedToJava) {
282+
return JavaNativeTypeManager.ToJniName (type);
283+
}
281284
string? j = JNIEnv.TypemapManagedToJava (type);
282285
if (j != null) {
283286
return GetReplacementTypeCore (j) ?? j;
284287
}
285-
if (JNIEnvInit.IsRunningOnDesktop) {
286-
return JavaNativeTypeManager.ToJniName (type);
287-
}
288288
return null;
289289
}
290290

291291
protected override IEnumerable<string> GetSimpleReferences (Type type)
292292
{
293-
string? j = JNIEnv.TypemapManagedToJava (type);
294-
j = GetReplacementTypeCore (j) ?? j;
295-
296-
if (JNIEnvInit.IsRunningOnDesktop) {
293+
string? j;
294+
if (RuntimeFeature.UseReflectionForManagedToJava) {
297295
string? d = JavaNativeTypeManager.ToJniName (type);
296+
j = GetReplacementTypeCore (d);
298297
if (j != null && d != null) {
299298
return new[]{j, d};
300299
}
301300
if (d != null) {
302301
return new[]{d};
303302
}
304303
}
304+
305+
j = JNIEnv.TypemapManagedToJava (type);
306+
j = GetReplacementTypeCore (j) ?? j;
305307
if (j != null) {
306308
return new[]{j};
307309
}

src/Mono.Android/Android.Runtime/JNIEnv.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,9 @@ public static string GetJniName (Type type)
446446
if (type == null)
447447
throw new ArgumentNullException ("type");
448448

449+
if (RuntimeFeature.UseReflectionForManagedToJava)
450+
return JavaNativeTypeManager.ToJniName (type);
451+
449452
string? java = TypemapManagedToJava (type);
450453
return java == null
451454
? JavaNativeTypeManager.ToJniName (type)

src/Mono.Android/Android.Runtime/JNIEnvInit.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ internal struct JnienvInitializeArgs {
2424
public int version; // TODO: remove, not needed anymore
2525
public int grefGcThreshold;
2626
public IntPtr grefIGCUserPeer;
27-
public int isRunningOnDesktop;
2827
public byte brokenExceptionTransitions;
2928
public int packageNamingPolicy;
3029
public byte ioExceptionType;
@@ -36,7 +35,6 @@ internal struct JnienvInitializeArgs {
3635
#pragma warning restore 0649
3736

3837
internal static JniRuntime.JniValueManager? ValueManager;
39-
internal static bool IsRunningOnDesktop;
4038
internal static bool jniRemappingInUse;
4139
internal static bool MarshalMethodsEnabled;
4240
internal static bool PropagateExceptions;
@@ -102,21 +100,12 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
102100
androidRuntime = new AndroidRuntime (args->env, args->javaVm, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0);
103101
ValueManager = androidRuntime.ValueManager;
104102

105-
IsRunningOnDesktop = args->isRunningOnDesktop == 1;
106-
107103
grefIGCUserPeer_class = args->grefIGCUserPeer;
108104
grefGCUserPeerable_class = args->grefGCUserPeerable;
109105

110106
PropagateExceptions = args->brokenExceptionTransitions == 0;
111107

112108
JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy;
113-
if (IsRunningOnDesktop) {
114-
var packageNamingPolicy = Environment.GetEnvironmentVariable ("__XA_PACKAGE_NAMING_POLICY__");
115-
if (Enum.TryParse (packageNamingPolicy, out PackageNamingPolicy pnp)) {
116-
JavaNativeTypeManager.PackageNamingPolicy = pnp;
117-
}
118-
}
119-
120109
SetSynchronizationContext ();
121110
}
122111

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
4+
namespace Android.Runtime;
5+
6+
static class RuntimeFeature
7+
{
8+
const string FeatureSwitchPrefix = "Android.Runtime.RuntimeFeature.";
9+
10+
[FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (UseReflectionForManagedToJava)}")]
11+
internal static bool UseReflectionForManagedToJava { get; } =
12+
AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (UseReflectionForManagedToJava)}", out bool isEnabled) ? isEnabled : false;
13+
}

src/Mono.Android/ILLink/ILLink.Substitutions.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@
44
<method signature="System.Boolean get_NegotiateAuthenticationIsEnabled()" body="stub" feature="Xamarin.Android.Net.UseNegotiateAuthentication" featurevalue="false" value="false" />
55
<method signature="System.Boolean get_NegotiateAuthenticationIsEnabled()" body="stub" feature="Xamarin.Android.Net.UseNegotiateAuthentication" featurevalue="true" value="true" />
66
</type>
7+
<type fullname="Android.Runtime.RuntimeFeature">
8+
<method signature="System.Boolean get_UseManagedTypeMaps()" body="stub" feature="Android.Runtime.RuntimeFeature.UseReflectionForManagedToJava" featurevalue="false" value="false" />
9+
<method signature="System.Boolean get_UseManagedTypeMaps()" body="stub" feature="Android.Runtime.RuntimeFeature.UseReflectionForManagedToJava" featurevalue="true" value="true" />
10+
</type>
711
</assembly>
812
</linker>

src/Mono.Android/Java.Interop/TypeManager.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,10 @@ static Exception CreateJavaLocationException ()
221221
if (type != null)
222222
return type;
223223

224-
if (!JNIEnvInit.IsRunningOnDesktop) {
225-
// Miss message is logged in the native runtime
226-
if (Logger.LogAssembly)
227-
JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true));
228-
return null;
229-
}
224+
// Miss message is logged in the native runtime
225+
if (Logger.LogAssembly)
226+
JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true));
227+
return null;
230228

231229
return null;
232230
}
@@ -368,7 +366,7 @@ public static void RegisterType (string java_class, Type t)
368366
if (String.Compare (jniFromType, java_class, StringComparison.OrdinalIgnoreCase) != 0) {
369367
TypeManagerMapDictionaries.ManagedToJni.Add (t, java_class);
370368
}
371-
} else if (!JNIEnvInit.IsRunningOnDesktop || t != typeof (Java.Interop.TypeManager)) {
369+
} else if (t != typeof (Java.Interop.TypeManager)) {
372370
// skip the registration and output a warning
373371
Logger.Log (LogLevel.Warn, "monodroid", FormattableString.Invariant ($"Type Registration Skipped for {java_class} to {t} "));
374372
}

src/Mono.Android/Mono.Android.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@
258258
<Compile Include="Android.Runtime\RequiresPermissionAttribute.cs" />
259259
<Compile Include="Android.Runtime\ResourceDesignerAttribute.cs" />
260260
<Compile Include="Android.Runtime\RuntimeConstants.cs" />
261+
<Compile Include="Android.Runtime\RuntimeFeature.cs" />
261262
<Compile Include="Android.Runtime\ResourceIdManager.cs" />
262263
<Compile Include="Android.Runtime\RuntimeNativeMethods.cs" />
263264
<Compile Include="Android.Runtime\StringDefAttribute.cs" />

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
1313
<PropertyGroup>
1414
<_AndroidRuntimePackRuntime>NativeAOT</_AndroidRuntimePackRuntime>
1515
<AndroidCodegenTarget Condition=" '$(AndroidCodegenTarget)' == '' ">JavaInterop1</AndroidCodegenTarget>
16+
<_AndroidUseReflectionForManagedToJava Condition=" '$(_AndroidUseReflectionForManagedToJava)' == '' ">true</_AndroidUseReflectionForManagedToJava>
1617
<!-- NativeAOT's targets currently gives an error about cross-compilation -->
1718
<DisableUnsupportedError Condition=" $([MSBuild]::IsOSPlatform('windows')) and '$(DisableUnsupportedError)' == '' ">true</DisableUnsupportedError>
1819
</PropertyGroup>

0 commit comments

Comments
 (0)