-
Notifications
You must be signed in to change notification settings - Fork 56
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
Switch JniEnvironment.FindClass
to use Class.forName instead of ClassLoader.loadClass
#23
Comments
…ass()` (#9769) Context: xamarin/monodroid@ed984a3 Context: dotnet/java-interop#23 Update `JNIEnv.FindClass()` to use `JniEnvironment.Types.FindClass()`. This avoids the problem of `JNIEnvInit.mid_Class_forName` being `null` in a NativeAOT context: E AndroidRuntime: Process: net.dot.hellonativeaot, PID: 30744 E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.TypeInitializationException: TypeInitialization_Type_NoTypeAvailable E AndroidRuntime: ---> System.ArgumentNullException: ArgumentNull_Generic Arg_ParamName_Name, method E AndroidRuntime: at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference, JniMethodInfo, JniArgumentValue*) + 0x1c8 E AndroidRuntime: at Android.Runtime.JNIEnv.FindClass(String) + 0xb4 E AndroidRuntime: at Java.Lang.Class..cctor() + 0x7c E AndroidRuntime: at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0xb4 E AndroidRuntime: Exception_EndOfInnerExceptionStack E AndroidRuntime: at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0x160 E AndroidRuntime: at System.Runtime.CompilerServices.ClassConstructorRunner.CheckStaticClassConstructionReturnNonGCStaticBase(StaticClassConstructionContext*, IntPtr) + 0x14 E AndroidRuntime: at Android.Runtime.JNIEnv.FindClass(String) + 0xc0 E AndroidRuntime: at Android.App.Application.get_Context() + 0x50 E AndroidRuntime: at Microsoft.Maui.Hosting.EssentialsExtensions.<>c.<UseEssentials>b__0_0(ILifecycleBuilder life) + 0x18 E AndroidRuntime: at Microsoft.Maui.LifecycleEvents.LifecycleEventService..ctor(IEnumerable`1) + 0x94 E AndroidRuntime: at Microsoft.Maui.LifecycleEvents.MauiAppHostBuilderExtensions.<>c.<ConfigureLifecycleEvents>b__0_0(IServiceProvider sp) + 0x3c E AndroidRuntime: at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite, RuntimeResolverContext) + 0x68 E AndroidRuntime: at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(ServiceIdentifier serviceIdentifier) + 0x180 E AndroidRuntime: at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey, Func`2) + 0x11c E AndroidRuntime: at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier, ServiceProviderEngineScope) + 0x44 E AndroidRuntime: at Microsoft.Maui.MauiContext.WrappedServiceProvider.GetService(Type) + 0x48 E AndroidRuntime: at Microsoft.Maui.MauiContext.WrappedServiceProvider.GetService(Type) + 0x48 E AndroidRuntime: at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider) + 0x3c E AndroidRuntime: at Microsoft.Maui.LifecycleEvents.LifecycleEventServiceExtensions.<GetLifecycleEventDelegates>d__3`1.MoveNext() + 0x38 E AndroidRuntime: at Microsoft.Maui.LifecycleEvents.LifecycleEventServiceExtensions.InvokeLifecycleEvents[TDelegate](IServiceProvider, Action`1) + 0x68 E AndroidRuntime: at Microsoft.Maui.MauiApplication.OnCreate() + 0xb0 E AndroidRuntime: at Android.App.Application.n_OnCreate(IntPtr jnienv, IntPtr native__this) + 0xb0 E AndroidRuntime: at my.MainApplication.n_onCreate(Native Method) E AndroidRuntime: at my.MainApplication.onCreate(MainApplication.java:24) E AndroidRuntime: at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1386) E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7398) E AndroidRuntime: at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0) E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2379) E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:107) E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:232) E AndroidRuntime: at android.os.Looper.loop(Looper.java:317) E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:8592) E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580) E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878) This also allows us to remove these fields that are now unused: * `JnienvInitializeArgs.Class_forName` * `JNIEnv.BinaryName()` TODO? While `JNIEnv.FindClass()` was using [`Class.forName()`][0], `JniEnvironment.Types.FindClass()` uses [`ClassLoader.loadClass()`][1]. `JNIEnv.FindClass()` originally used `ClassLoader.loadClass()`, but was changed to use `Class.forName()` in xamarin/monodroid@ed984a3a as part of supporting the Xamarin.Android Designer. Updating `JNIEnv.FindClass()` to use `JniEnvironment.Types.FindClass()` effectively reverts that original change. This is "fine" for now -- the Xamarin.Android Designer is no longer supported -- but in the future we may want to update `JniEnvironment.Types.FindClass()` to use `Class.forName()`; see dotnet/java-interop#23. [0]: https://developer.android.com/reference/java/lang/Class#forName(java.lang.String) [1]: https://developer.android.com/reference/java/lang/ClassLoader#loadClass(java.lang.String)
The commit message of xamarin/monodroid@ed984a provides some useful context:
Which raises an interesting aside: Simply replacing |
Fixes: #23 Context: dotnet/android@aba2726 Context: b4d44e4 Context: xamarin/monodroid@ed984a3 Context: dotnet/android#7616 Commit b4d44e4 noted: > Android is..."special", in that not all threads get the same > ClassLoader behavior. Specifically, *managed* threads -- > System.Threading.Thread instances -- get a different ClassLoader than > the main/UI thread on Android. (Untested, but the ClassLoader *may* > behave sanely if you use a java.lang.Thread instance instead. But who > wants to use java.lang.Thread instances...?) dotnet/android#7616 provided additional context: `JNIEnv::FindClass()` behavior *is* tied to the thread that calls it, and one of the knock-on effects is that `Java.Lang.JavaSystem.LoadLibrary("MyLib")` doesn't work properly when invoked from a new `System.Threading.Thread` thread. Which brings us to xamarin/mondroid@ed984a3a, which updated then Xamarin.Android to use [`java.lang.Class.forName(String)`][0] instead of [`ClassLoader.loadClass(String)`][1] because the "real" JDK cannot use `ClassLoader.loadClass(String)` to load array types: Class c1 = Class.forName("[Ljava.lang.String;"); // works Class c2 = ClassLoader.getSystemClassLoader() .loadClass("[Ljava.lang.String;"); // throws java.lang.ClassNotFoundException Class c3 = Class.forName("[I"); // works; array of int Class c4 = ClassLoader.getSystemClassLoader() .loadClass("[I"); // throws java.lang.ClassNotFoundException Using `ClassLoader.loadClass(String)` to load array types works on Android, presumably as an undocumented implementation detail, but as xamarin/monodroid@ed984a3a was trying to get things working within the (now dead) Android Designer -- which ran using a Desktop JDK -- Android-specific extensions were not available. Update `JniEnvironment.Types` to use `Class.forName(String)` instead of `ClassLoader.loadClass(String)` to load Java classes, as a fallback for when `JNIEnv::FindClass()` fails to find the class. [0]: https://developer.android.com/reference/java/lang/Class#forName(java.lang.String) [1]: https://developer.android.com/reference/java/lang/ClassLoader#loadClass(java.lang.String)
This is a fun demo app: class Example {
public static void main(String[] args) throws Throwable {
String[] types = {
"java.lang.String", "java/lang/String",
"[Ljava/lang/String;", "[Ljava.lang.String;", "[java.lang.String", "java.lang.String[]",
"I", "int", "[I", "[int", "int[]",
"[Ljava.lang.Thread$UncaughtExceptionHandler;",
// "int", "long", "float", "double", "char", "byte", "short", "boolean"
};
ClassLoader cl = ClassLoader.getSystemClassLoader();
for (String type : types) {
try {
Class<?> c = Class.forName(type);
System.out.println("Class.forName(" + type + ") found: " + c.getName()); // prints "int", "long", etc.
} catch (ClassNotFoundException e) {
System.out.println("error: Class.forName(" + type + ") failed: " + e.toString());
}
try {
Class<?> c = cl.loadClass(type);
System.out.println("ClassLoader.loadClass(" + type + ") found: " + c.getName()); // prints "int", "long", etc.
} catch (ClassNotFoundException e) {
System.out.println("error: ClassLoader.loadClass(" + type + ") failed: " + e.toString());
}
}
Class as = String[].class;
System.out.println("String[].class.getName()=" + as.getName()); // prints "[Ljava.lang.String;"
System.out.println("Thread.UncaughtExceptionHandler[].class.getCanonicalName()=" + Thread.UncaughtExceptionHandler[].class.getName()); // prints "java.lang.String[]"
}
} output:
The TL;DR:
|
…#1326) Fixes: #23 Context: dotnet/android@aba2726 Context: b4d44e4 Context: xamarin/monodroid@ed984a3 Context: dotnet/android#7616 Commit b4d44e4 noted: > Android is..."special", in that not all threads get the same > ClassLoader behavior. Specifically, *managed* threads -- > System.Threading.Thread instances -- get a different ClassLoader than > the main/UI thread on Android. (Untested, but the ClassLoader *may* > behave sanely if you use a java.lang.Thread instance instead. But who > wants to use java.lang.Thread instances...?) dotnet/android#7616 provided additional context: `JNIEnv::FindClass()` behavior *is* tied to the thread that calls it, and one of the knock-on effects is that `Java.Lang.JavaSystem.LoadLibrary("MyLib")` doesn't work properly when invoked from a new `System.Threading.Thread` thread. (This is still the case, by the way.) Which brings us to xamarin/mondroid@ed984a3a, which updated then Xamarin.Android to use [`Class.forName(String name, boolean initialize, ClassLoader loader)`][0] instead of [`ClassLoader.loadClass(String)`][1], because the "real" JDK cannot use `ClassLoader.loadClass(String)` to load array types: Class c1 = Class.forName("[Ljava.lang.String;"); // works Class c2 = ClassLoader.getSystemClassLoader() .loadClass("[Ljava.lang.String;"); // throws java.lang.ClassNotFoundException Class c3 = Class.forName("[I"); // works; array of int Class c4 = ClassLoader.getSystemClassLoader() .loadClass("[I"); // throws java.lang.ClassNotFoundException Using `ClassLoader.loadClass(String)` to load array types works on Android, presumably as an undocumented implementation detail, but as xamarin/monodroid@ed984a3a was trying to get things working within the (now dead) Android Designer -- which ran using a Desktop JDK -- Android-specific extensions were not available. Update `JniEnvironment.Types` to use `Class.forName(String)` instead of `ClassLoader.loadClass(String)` to load Java classes, as a fallback for when `JNIEnv::FindClass()` fails to find the class. `[Obsolete]` the `JniRuntime.CreationOptions.ClassLoader_LoadClass_id` property as it is no longer used. [0]: https://developer.android.com/reference/java/lang/Class#forName(java.lang.String,%20boolean,%20java.lang.ClassLoader) [1]: https://developer.android.com/reference/java/lang/ClassLoader#loadClass(java.lang.String)
Fixes: dotnet/java-interop#23 Changes: dotnet/java-interop@bc44f08...5852e6e * dotnet/java-interop@5852e6e3: [Java.Interop] Use `Class.forName()` as fallback to load Java classes (dotnet/java-interop#1326)
Fixes: dotnet/java-interop#23 Changes: dotnet/java-interop@bc44f08...5852e6e * dotnet/java-interop@5852e6e3: [Java.Interop] Use `Class.forName()` as fallback to load Java classes (dotnet/java-interop#1326)
See equivalent commit in monodroid: https://github.com/xamarin/monodroid/commit/ed984a3a0bfbe71f6499a7855c647b5bc2b28466
The text was updated successfully, but these errors were encountered: