Skip to content
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

JavaKit: Class loader hints for Android #154

Closed
lhoward opened this issue Nov 6, 2024 · 5 comments · Fixed by #159
Closed

JavaKit: Class loader hints for Android #154

lhoward opened this issue Nov 6, 2024 · 5 comments · Fixed by #159
Labels
android help wanted Extra attention is needed javakit

Comments

@lhoward
Copy link
Contributor

lhoward commented Nov 6, 2024

Android JVM class resolution is tricky, as which classes are resolvable depends not only on the JNI environment but also the stack frame.

From https://developer.android.com/training/articles/perf-jni:

You can get into trouble if you create a thread yourself (perhaps by calling pthread_create and then attaching it with AttachCurrentThread). Now there are no stack frames from your application. If you call FindClass from this thread, the JavaVM will start in the "system" class loader instead of the one associated with your application, so attempts to find app-specific classes will fail.

There are a few ways to work around this:

  • Do your FindClass lookups once, in JNI_OnLoad, and cache the class references for later use. Any FindClass calls made as part of executing JNI_OnLoad will use the class loader associated with the function that called System.loadLibrary (this is a special rule, provided to make library initialization more convenient). If your app code is loading the library, FindClass will use the correct class loader.
  • Pass an instance of the class into the functions that need it, by declaring your native method to take a Class argument and then passing Foo.class in.
  • Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.

Doing FindClass() lookups in JNI_OnLoad isn't really viable unless JavaKit caches classes and we can ’poison’ the cache by resolving any classes we will need there. It's is not ideal though as it requires a priori knowledge of all the application classes (I suppose, this could be done with a macro). Conversely, passing an instance of the class into functions that need it doesn't make for a great API. Neither of these is particularly ergonomic because getJNIClass() can't be overridden (it's in an extension), so one ends up needing to reimplement dynamicJavaNewObjectInstance().

Caching a reference to the ClassLoader object might be the best approach, this can be done in JNI_OnLoad (as long as you know the name of at least one application class), and then perhaps it could be an additional optional argument to initialisers. Or JNIEnvironment becomes a class to which class loaders can be attached.

Anyway, just flagging this as food for thought.

@lhoward
Copy link
Contributor Author

lhoward commented Nov 6, 2024

As a test, I cached the class loader in JNI_OnLoad, and implemented my own FindClass() that used it. (I thought I might be able to swizzle the JNI env, which whilst ugly would be quite convenient, but no dice – not writable memory.)

This gets me a few steps further but plenty of things like as() are broken. So this is a bit of a tricky one. I think this is going to come up in any non-trivial Android application because you're likely going to need to allocate Java objects from the Swift side (edit: and from multiple threads).

@lhoward
Copy link
Contributor Author

lhoward commented Nov 6, 2024

In theory this shouldn't be a problem if we resolve the classes directly within the native method entry point (as in, not JNI_OnLoad but the first API call). Needs more investigation at this end, ISTR perhaps classes were visible in OnLoad but not from application methods. Gah.

@lhoward
Copy link
Contributor Author

lhoward commented Nov 6, 2024

Maybe we could have an optional protocol a Swift class could adopt that returns the preferred class loader as a static variable. That should be fairly easy to prototype at least.

@lhoward
Copy link
Contributor Author

lhoward commented Nov 7, 2024

OK, I think something like this might work, still testing though.

lhoward added a commit to PADL/swift-java that referenced this issue Nov 7, 2024
Swift projections of Java classes can conform to
CustomJavaClassLoaderConstructible, to provide a custom class loader to be used
when constructing new instances.

Fixes: swiftlang#154
lhoward added a commit to PADL/swift-java that referenced this issue Nov 7, 2024
Swift projections of Java classes can conform to
CustomJavaClassLoaderConstructible, to provide a custom class loader to be used
when constructing new instances.

Fixes: swiftlang#154
@ktoso ktoso added help wanted Extra attention is needed javakit android labels Nov 7, 2024
@lhoward lhoward changed the title Class loader hints for Android JavaKit: Class loader hints for Android Nov 7, 2024
@lhoward
Copy link
Contributor Author

lhoward commented Nov 7, 2024

Confirmed this approach works (although still some issues casting with as(), may be related to #157.

lhoward added a commit to PADL/swift-java that referenced this issue Nov 7, 2024
Swift projections of Java classes can conform to CustomJavaClassLoader, to
provide a custom class loader to be used when initializing new instances. This
is useful for platforms such as Android which modify class and class loader
visibility depending on the call stack.

Fixes: swiftlang#154
lhoward added a commit to PADL/swift-java that referenced this issue Nov 7, 2024
Swift projections of Java classes can conform to CustomJavaClassLoader, to
provide a custom class loader to be used when initializing new instances. This
is useful for platforms such as Android which modify class and class loader
visibility depending on the call stack.

Fixes: swiftlang#154
lhoward added a commit to PADL/swift-java that referenced this issue Nov 7, 2024
Swift projections of Java classes can conform to CustomJavaClassLoader, to
provide a custom class loader to be used when initializing new instances. This
is useful for platforms such as Android which modify class and class loader
visibility depending on the call stack.

Fixes: swiftlang#154
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
android help wanted Extra attention is needed javakit
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants