Search code examples
javacjava-native-interfacejnienv

Calling Java methods from C without starting the JVM from C


I am looking for a tutorial for how to call Java methods from C using JNI. In all the tutorials that I've found so far, the examples show how to first create a JVM from C.

My application starts from Java and uses JNI to call some C functions. I now need to call some Java functions from C and I don't want to start the JVM from C.

Is it possible, for instance, to create a "native" method implemented in C, and to use it in order to save the JNIEnv pointer, and then to reuse it, instead of creating a new JVM instance from C, in order to call Java methods?

Is there an example for it?


EDIT:

Whoever reads this thread should be careful! Only use the JNIEnv* instance that comes from the current JNI call! Do not save and reuse any JNIEnv* pointer that you got before, if you don't want your program to crash.

Using and old saved pointer, could in the best scenario crash your application. In worse scenarios, it can cause low-level inconsistency problems that are very hard to debug and understand

No JNIEnv* instance should be cached, since if your program starts from Java, then your C code will anyway be able to execute only as a native java method, that its C implementation always gets a relevant JNIEnv* instance. This is the only instance that you should use until your native method returns, and not any old saved instance.


Solution

  • As JJF stated, this is definitely possible! I'll show an example below, assuming you already know how to call Java methods from C, but without having to create a JVM!

    First, we have a Java class test.Test.java that calls the native method methodA:

    package test;
    
    public class Test {
        static {
            System.loadLibrary("test");
        }
    
        private native void methodA();
    
        public static void methodB() {
            System.out.println("Java: Method B has executed!");
        }
    
        public static void main(String[] args) {
            new Test().methodA();
        }
    }
    

    Next, we have the header file created by javah or javac -h:

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class test_Test */
    
    #ifndef _Included_test_Test
    #define _Included_test_Test
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     test_Test
     * Method:    methodA
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_test_Test_methodA
      (JNIEnv *, jobject);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    Below is the C file which holds the native method methodA:

    #include <jni.h>
    #include <stdio.h>
    #include "test_Test.h"
    
    
    void callMethodB(JNIEnv *env);
    
    JNIEXPORT void JNICALL Java_test_Test_methodA(JNIEnv *env, jobject thisObj) {
        printf("C: Method A executed!\n"); fflush(stdout);
        callMethodB(env);
        return;
    }
    
    void callMethodB(JNIEnv *env) {
        jclass testClass = (*env) -> FindClass(env, "test/Test");
        jmethodID methodB = (*env) -> GetStaticMethodID(env, testClass, "methodB", "()V");
        (*env) -> CallStaticVoidMethod(env, testClass, methodB, NULL);
        return;
    }
    

    The output of this program is:

    C: Method A executed!

    Java: Method B has executed!

    Remember to create a test.dll file with your preferred GNU compiler.