Skip to content

Commit 199efa8

Browse files
authored
[NativeAOT] Provide the Android ClassLoader (#10121)
Fixes: #10118 Context: 18ca528 Context: dotnet/java-interop@5852e6e dotnet/java-interop@5852e6e3 updated `JniEnviroment.Types.FindClass()` to begin using [`Class.forName(String, bool, ClassLoader)`][0] to load Java types instead of `ClassLoader.loadClass()`. This broke type registration on non-Java threads under NativeAOT; attempt to load a non-Android Java type from a managed thread: var t = new System.Threading.Thread(() => { using var c = new ClassFromThisAssembly(); }); t.Start(); t.Join(); would fail with a `ClassNotFoundException`: E NUnit : Java.Lang.ClassNotFoundException: Didn't find class "from.NewManagedThreadOne" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /system/lib64, /system_ext/lib64]] E NUnit : at Java.Interop.JniEnvironment.Types.TryFindClass(String, Boolean) + 0x3f4 E NUnit : at Java.Interop.JniPeerMembers.JniInstanceMethods..ctor(Type) + 0x130 E NUnit : at Java.Interop.JniPeerMembers.JniInstanceMethods.GetConstructorsForType(Type) + 0x94 E NUnit : at Java.Interop.JniPeerMembers.JniInstanceMethods.StartCreateInstance(String, Type, JniArgumentValue*) + 0x1c E NUnit : at Java.Lang.Object..ctor() + 0x108 E NUnit : at Java.InteropTests.JnienvTest.<>c__DisplayClass6_0.<RegisterTypeOnNewManagedThread>b__0() + 0x24 E NUnit : --- End of managed Java.Lang.ClassNotFoundException stack trace --- E NUnit : java.lang.ClassNotFoundException: Didn't find class "from.NewManagedThreadOne" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /system/lib64, /system_ext/lib64]] E NUnit : at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:259) E NUnit : at java.lang.ClassLoader.loadClass(ClassLoader.java:637) E NUnit : at java.lang.ClassLoader.loadClass(ClassLoader.java:573) Fix this by setting `NativeAotRuntimeOptions.ClassLoader` to the `context.getClassLoader()` value within `NativeAotRuntimeProvider.attachInfo()`. This ensures that we use a `ClassLoader` that knows about the app's `classes.dex`. [0]: https://developer.android.com/reference/java/lang/Class#forName(java.lang.String,%20boolean,%20java.lang.ClassLoader)
1 parent fc296cd commit 199efa8

File tree

4 files changed

+30
-6
lines changed

4 files changed

+30
-6
lines changed

src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
3131

3232
// symbol name from `$(IntermediateOutputPath)obj/Release/osx-arm64/h-classes/net_dot_jni_hello_JavaInteropRuntime.h`
3333
[UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_nativeaot_JavaInteropRuntime_init")]
34-
static void init (IntPtr jnienv, IntPtr klass)
34+
static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader)
3535
{
3636
JniTransition transition = default;
3737
try {
@@ -41,6 +41,7 @@ static void init (IntPtr jnienv, IntPtr klass)
4141
var typeManager = new ManagedTypeManager ();
4242
var options = new NativeAotRuntimeOptions {
4343
EnvironmentPointer = jnienv,
44+
ClassLoader = new JniObjectReference (classLoader),
4445
TypeManager = typeManager,
4546
ValueManager = new ManagedValueManager (),
4647
UseMarshalMemberBuilder = false,

src/Xamarin.Android.Build.Tasks/Resources/JavaInteropRuntime.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ public class JavaInteropRuntime {
1111
private JavaInteropRuntime() {
1212
}
1313

14-
public static native void init();
14+
public static native void init(ClassLoader classLoader);
1515
}

src/Xamarin.Android.Build.Tasks/Resources/NativeAotRuntimeProvider.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ public void attachInfo(android.content.Context context, android.content.pm.Provi
3737
Log.e(TAG, "Failed to set environment variables", e);
3838
}
3939

40+
ClassLoader loader = context.getClassLoader ();
41+
4042
// Initialize .NET runtime
41-
JavaInteropRuntime.init();
43+
JavaInteropRuntime.init(loader);
4244
// NOTE: only required for custom applications
4345
ApplicationRegistration.registerApplications();
4446
super.attachInfo (context, info);

tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public void TestMyPaintColor ()
4444
public void RegisterTypeOnNewNativeThread ()
4545
{
4646
Java.Lang.JavaSystem.LoadLibrary ("reuse-threads");
47-
int ret = rt_register_type_on_new_thread ("from.NewThreadOne", Application.Context.ClassLoader.Handle);
47+
int ret = rt_register_type_on_new_thread ("from.NewNativeThreadOne", Application.Context.ClassLoader.Handle);
4848
Assert.AreEqual (0, ret, $"Java type registration on a new thread failed with code {ret}");
4949
}
5050

@@ -57,6 +57,23 @@ public void RegisterTypeOnNewJavaThread ()
5757
Assert.AreNotEqual (null, thread.Instance, "Failed to register instance of a class on new thread");
5858
}
5959

60+
[Test]
61+
public void RegisterTypeOnNewManagedThread ()
62+
{
63+
Exception? ex = null;
64+
var thread = new System.Threading.Thread (() => {
65+
try {
66+
using var instance = new RegisterMeOnNewManagedThreadOne ();
67+
}
68+
catch (Exception e) {
69+
ex = e;
70+
}
71+
});
72+
thread.Start ();
73+
thread.Join (5000);
74+
Assert.IsNull (ex, $"Failed to register instance of a class on new thread: {ex}");
75+
}
76+
6077
[Test]
6178
public void ThreadReuse ()
6279
{
@@ -459,8 +476,12 @@ public void DoNotLeakWeakReferences ()
459476
}
460477
}
461478

462-
[Register ("from/NewThreadOne")]
463-
class RegisterMeOnNewThreadOne : Java.Lang.Object
479+
[Register ("from/NewNativeThreadOne")]
480+
class RegisterMeOnNewNativeThreadOne : Java.Lang.Object
481+
{}
482+
483+
[Register ("from/NewManagedThreadOne")]
484+
class RegisterMeOnNewManagedThreadOne : Java.Lang.Object
464485
{}
465486

466487
[Register ("from/NewThreadTwo")]

0 commit comments

Comments
 (0)