Search code examples
javaandroidcjava-native-interfacejnienv

Call a function in java from C outside of a JNI function (Android)?


I'm trying to call a Java function from my C code using the JNI in Android, but I'm in a somewhat awkward situation.

My C code is being executed outside of a JNI function in a callback that is passed to a library.

Here is an example of the java code

package com.my.java.package;

class MyClass {
    public function handleData(byte[] data) {
        doSomethingWithThisData(data);
    }
}

Here is an example of the C code

void handleData(uint8_t *data, size_t len) {
    // I need to call handleData in my java
    // class instance from here, but i have
    // no access to a JNIEnv here.

    // I don't think I can create one, since 
    // it has to be the same object that's 
    // sending JNI calls elsewhere.
}

. . . 

myCLibInstance.callback = handleData;

Now whenever the C Lib does what it needs to do, it will trigger the callback. But I have no way to send it back to the java class to handle the data.


Solution

  • I noticed some issues with Brandons Solution, specifically around the way that you handle status codes and the unnecessary getJavaVM function, so i made some alterations and added some notes. This is the only functional version of this I have managed to get working.

    Note that for some reason, the JNIEnv* returned by getJNIEnv() does not work with the Java class loader when used from another thread. I'm unsure why. So in this example I store static instances to the classes, loading them directly in the JNI_OnLoad function and use them later when needed.

    If anyone knows a work around to get the JNIEnv* returned by getJNIEnv() to support the Java class loader from other threads, let me know.

    // JavaVM instance stored after JNI_OnLoad is called
    JavaVM* javaVM = NULL;
    
    // Since the class loader will not work with getJNIEnv(),
    // you can store classes in GlobalRefs.
    static jclass my_class_class;
    
    /**
     * Load the JNIEnv and store the JavaVM instance for ater calls to getJNIEnv().
     *
     * @param jvm      The Java VM
     * @param reserved Reserved pointer
     *
     * @return         The supported version of JNI.
     */
    JNIEXPORT jint JNICALL
    JNI_OnLoad(
            JavaVM* jvm,
            void* reserved
    ) {
        javaVM = jvm;
    
        // Here we load the classes since getJNIEnv() does 
        // not work with the class loader from other threads
        JNIEnv* env = getJNIEnv();
        my_class_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/my/java/package/MyClass"));
    
        // Return the supported JNI version.
        return JNI_VERSION_1_6;
    }
    
    /**
     * Retrieve an instance of JNIEnv to use across threads.
     *
     * Note that the class loader will not work with this instance (unsure why).
     *
     * @return a JNIEnv instance
     */
    JNIEnv* getJNIEnv() {
        JNIEnv *env;
    
        // If the current thread is not attached to the VM, 
        // sets *env to NULL, and returns JNI_EDETACHED.
        //
        // If the specified version is not supported, sets *env to NULL, 
        // and returns JNI_EVERSION.
        //
        // Otherwise, sets *env to the appropriate interface, and returns JNI_OK.
        int status = (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_6);
    
        // Check if the JVM is not currently attached to the
        // calling thread, and if so attempt to attach it.
        if (status == JNI_EDETACHED) {
            // Attaches the current thread to a Java VM.
            // Returns a JNI interface pointer in the JNIEnv argument.
            status = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
        }
    
        // If the result of GetEnv was JNI_EVERSION,
        // we want to abort.
        assert(status != JNI_EVERSION);
    
        // Return the ENV if we have one
        return env;
    }
    
    void handleData(uint8_t *data, size_t len) {
        JNIEnv* env = getJNIEnv();
    
        // ... call jni function using env and my_class_class ...
    }