java-native-access / jna

Java Native Access
Other
8.47k stars 1.67k forks source link

Support JNI_OnLoad for running static constructors #1019

Open ncalexan opened 5 years ago

ncalexan commented 5 years ago

The JNI invokes JNI_OnLoad when loading a library. I was surprised that Native.loadLibrary(...) did not invoke JNI_OnLoad in my library (when it is present). Could we mimic the JNI functionality? This is an observable behaviour difference.

I understand that this is not the most natural situation for JNA -- I believe JNA is intended to provide access to existing libraries that are not intended specifically for use with JNI -- but it's mighty useful.

I searched the issues and the JNA users group and can find no mention of this type of feature request.

matthiasblaesing commented 5 years ago

I don't see the use-case. If the library is already callable by JNI, I would call it by JNI. The big benefit I see in JNA is, that I don't need to build native code to bind C<->Java, if the library is already prepared, what do you gain by using JNA?

ncalexan commented 5 years ago

I don't see the use-case. If the library is already callable by JNI, I would call it by JNI.

This is fair, but for JNI_OnLoad behaviour it means doing a lot of platform-specific things, which look like __attribute__((constructor)) or its less standard cousins. And this requires the library consumer to invoke a method, which can be difficult to co-ordinate when multiple library consumers are involved.

The big benefit I see in JNA is, that I don't need to build native code to bind C<->Java, if the library is already prepared, what do you gain by using JNA?

That's a good question. I'm honestly not sure we're gaining enough to make it worth while, but right now we're using ByReference to communicate rich result types out of a Rust library back to Java. I think that there are many ways to skin that cat, and many of them don't run through JNA, but this one is working for us right now. Since we control the underlying Rust library, JNA's main use case of binding to existing symbols isn't so valuable, so maybe we should hand roll these ByReference things and drop down to pure JNI?

matthiasblaesing commented 5 years ago

I don't see the use-case. If the library is already callable by JNI, I would call it by JNI.

This is fair, but for JNI_OnLoad behaviour it means doing a lot of platform-specific things, which look like __attribute__((constructor)) or its less standard cousins. And this requires the library consumer to invoke a method, which can be difficult to co-ordinate when multiple library consumers are involved.

Ok - to accomplish this, you'd need to change the native side of the library loader and even then, it is possible, that the initialization would run multiple times (there are multiple ways to load libraries in JNA and only some of them run under an existing lock). With 5.0.0 the theme was: remove locking and introducing a new possible dead-lock source sounds dangerous. I would be willing to review, but I won't work on it myself.

The big benefit I see in JNA is, that I don't need to build native code to bind C<->Java, if the library is already prepared, what do you gain by using JNA?

That's a good question. I'm honestly not sure we're gaining enough to make it worth while, but right now we're using ByReference to communicate rich result types out of a Rust library back to Java. I think that there are many ways to skin that cat, and many of them don't run through JNA, but this one is working for us right now. Since we control the underlying Rust library, JNA's main use case of binding to existing symbols isn't so valuable, so maybe we should hand roll these ByReference things and drop down to pure JNI?

The ByReference types are basicly:

thomcc commented 5 years ago

For further context, I work on the same project as @ncalexan, and what we get out of using JNA over JNI is the ability to expose our code a single time with a C ABI, and use it on both iOS and Android. We do this for several libraries in https://github.com/mozilla/application-services (which provides things like Sync and Storage). For most of these, that's fine.

Occasionally, we have reasons to want more control over the Android bindings, and so would like to use JNI for them. However, these libraries are compiled into a single shared object (to avoid including one copy of the Rust stdlib per library, but also to allow global state set up by one library to be visible in another -- for example, to allow Android to receive intercept logging, network requests, etc. made by Rust code), and so we can't just use a separate .so that is loaded with JNI instead.

Anyway, your comment of "I would be willing to review, but I won't work on it myself." is fine by us (so far we've been able to find workarounds), but I did want to clarify the reason behind this.

saudet commented 5 years ago

@thomcc With JavaCPP, the way that we go about it is to expose JNI to Java itself. For example, there's a Loader.loadGlobal() function annotated as @Raw to have it treated more or less as a JNI function so that it can get access to the environment and throw Java exceptions back when necessary: https://github.com/bytedeco/javacpp/blob/master/src/main/java/org/bytedeco/javacpp/Loader.java#L1611 We also sometimes need access to the JavaVM object, so JavaCPP also provides a getJavaVM() function for that, to allow passing it back to other libraries that use JNI such as FFmpeg on Android: https://github.com/bytedeco/javacpp/blob/master/src/main/java/org/bytedeco/javacpp/Loader.java#L1616 That works pretty well, and I'm sure we could do the same with JNA.