Skip to content

[NativeAOT] Provide the Android ClassLoader #10121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2025

Conversation

jonpryor
Copy link
Contributor

@jonpryor jonpryor commented May 7, 2025

Fixes: #10081
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) to load Java types instead of ClassLoader.loadClass().

This broke type registration on non-Java threads under NativeAOT; attempting 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.

Fixes: #10081
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;
attempting 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)
@jonpryor jonpryor requested a review from jonathanpeppers as a code owner May 7, 2025 18:46
@jonpryor jonpryor requested review from Copilot and jonathanpeppers and removed request for jonathanpeppers May 7, 2025 18:46
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes issues with Java type registration on non-Java threads under NativeAOT by ensuring that a proper Android ClassLoader is passed during initialization. The changes include updating the tests to use new class registration names for native and managed threads, modifying the JavaInteropRuntime API to accept a ClassLoader parameter, and updating the native initialization method on the C# side.

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs Updated tests to use new registration strings and added a new test for managed thread type registration.
src/Xamarin.Android.Build.Tasks/Resources/NativeAotRuntimeProvider.java Retrieves the app ClassLoader and passes it to the JavaInteropRuntime initializer.
src/Xamarin.Android.Build.Tasks/Resources/JavaInteropRuntime.java Updated native method signature to accept a ClassLoader argument.
src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs Modified native initialization to accept and store the ClassLoader for proper type registration.

}
});
thread.Start ();
thread.Join (5000);
Copy link
Preview

Copilot AI May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider extracting the magic number 5000 into a named constant for improved clarity and maintainability in test code.

Suggested change
thread.Join (5000);
thread.Join (ThreadJoinTimeoutMs);

Copilot uses AI. Check for mistakes.

Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new test passes:

image

@jonathanpeppers
Copy link
Member

Note that I'm not sure if this actually fixes:

I think their sample was running on Mono, but I asked to double-check.

@jonpryor jonpryor merged commit 199efa8 into main May 8, 2025
59 checks passed
@jonpryor jonpryor deleted the dev/jonp/jonp-NativeAOT-set-ClassLoader branch May 8, 2025 12:48
@github-actions github-actions bot locked and limited conversation to collaborators Jun 8, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
2 participants