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

don't rely on JNI_GetCreatedJavaVMs #199

Open
user16332 opened this issue Nov 23, 2023 · 11 comments
Open

don't rely on JNI_GetCreatedJavaVMs #199

user16332 opened this issue Nov 23, 2023 · 11 comments

Comments

@user16332
Copy link

For calls from Haskell into the JVM jni relies on a cached pointer to JNI's JavaVM struct. This pointer is currently obtained via the jvm function that calls JNI_GetCreatedJavaVMs: https://github.com/tweag/inline-java/blob/master/jni/src/common/Foreign/JNI/Unsafe/Internal.hs#L363 This call isn't available on Android.

One way to obtain this pointer is through the JNIEnv struct that is being passed by JNI on calls from Java (it has a member function GetJavaVM), and cache it in a global variable. This commit provides a Haskell jniInit function that users of the package have to call before making any calls back into Java. user16332@affeae4#diff-513af56a87e02a1e7a882ec00c88255da344d42b129da7da6308b34ac37408b9R361

Another solution would be to implement JNI_OnLoad which provides the pointer.

@user16332
Copy link
Author

Some more thoughts:

  • there are basically two different Java interop approaches. Either you have a Haskell executable (Haskell main) and call into a JVM that you create from Haskell via withJVM. I suspect the sparkle use case that this library was created for works like that. Or you have a JVM executable (Java main) and load a Haskell library. On Android only the latter option is available.
  • there could be a special Android version enabled by a package flag
  • as for the JNI_OnLoad option, could this even call hs_init()?

@facundominguez
Copy link
Member

facundominguez commented Nov 26, 2023

I suspect the sparkle use case that this library was created for works like that

In the case of sparkle, the JVM is calling into Haskell.

as for the JNI_OnLoad option, could this even call hs_init()?

Maybe JNI_OnLoad could store the JavaVM pointer in some static location, and Haskell could read it from there. In the case of sparkle, hs_init() is called with some explicit call from the Java side IIRC.

@user16332
Copy link
Author

user16332 commented Nov 26, 2023

In the case of sparkle, the JVM is calling into Haskell.

Ah well that's just like Android then, and I also have a desktop JavaFX app which works like that. So we have the same use case here.

Maybe JNI_OnLoad could store the JavaVM pointer in some static location, and Haskell could read it from there. In the case of sparkle, hs_init() is called with some explicit call from the Java side IIRC.

You do what you think is best.

@facundominguez
Copy link
Member

Putting JNI_OnLoad in jni is going to be problematic for any user that intends to use JNI_OnLoad for a different purpose. That leaves us with putting in user hands the reponsibility of providing a pointer to the JVM in platforms that don't support JNI_GetCreatedJavaVMs.

A possible design is to take the JVM pointer with an API call like:

setJVM :: Ptr JVM -> IO ()

When unset, the JVM is the value returned by JNI_GetCreatedJavaVMs in platforms that support such call. Otherwise, it is NULL.

@user16332
Copy link
Author

user16332 commented Nov 30, 2023

Sounds like a plan but where does the code calling setJVM get the pointer to the JVM from? My jniInit example above is essentially that but it takes a JNIEnv which is passed to any native function being called from the JVM.

@facundominguez
Copy link
Member

There is at least GetJavaVM and JNI_OnLoad to get it.

@user16332
Copy link
Author

Yes both are not exposed by the jni library. how does Haskell code get this pointer. Add GetJavaVM to jni? That would make sense. It would be the jniInit code above split into two parts.

@facundominguez
Copy link
Member

how does Haskell code get this pointer.

I don't know how to solve it in Haskell. I guess it is the same trouble that users would have to set the jniJvm pointer with your patch. When I propose GetJavaVM and JNI_OnLoad, it is to use them in the C side before going into Haskell.

@facundominguez
Copy link
Member

On second thought, we could offer the call in C

void io_tweag_jni_set_jvm(JavaVM*)

so the user doesn't need to cross the language boundary to initialize the pointer.

@user16332
Copy link
Author

No too sure how it all comes together but I'd like to avoid having the user have to implement a C shim. Currently with jniInit my solution is pure Haskell + Java only:

In Java (Kotlin actually), using JNA:

import android.app.Application
import android.content.Context
import com.sun.jna.JNIEnv
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.Pointer
import java.lang.Boolean.TRUE
import java.util.Collections

class MyApp: Application() {
    private interface AppLib: Library {
        fun hs_init(p1: Pointer?, p2: Pointer?)
        fun app_start(env: JNIEnv, ctx: Context)
    }

    override fun onCreate() {
        super.onCreate()
        val lib = Native.load("app", AppLib::class.java,
            Collections.singletonMap(Library.OPTION_ALLOW_OBJECTS, TRUE))
        lib.hs_init(Pointer.NULL, Pointer.NULL)
        lib.app_start(JNIEnv.CURRENT, this)
    }
}

In Haskell:

foreign export ccall "app_start" start :: Ptr JNIEnv -> Ptr JContext -> IO ()

start :: Ptr JNIEnv -> Ptr JContext -> IO ()
start jniEnv jctx = do
  jniInit jniEnv
  ... further initialization ..

@facundominguez
Copy link
Member

Oh, I see what you mean now. In that case it would make sense to expose GetJavaVM in Haskell, indeed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants