Search code examples
javacjava-native-interfacejnienv

Calling Java methods from C without starting the JVM from C


All examples I've found so far for calling a Java function from C, show how to first create/start a JVM from C. But In my case, the application starts from Java and the C function is called from Java.

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:

  • The question above is based on a misunderstanding. Every native method (A Java method implemented in C) is supplied with an up-tp-date JNIEnv pointer as a part of its signature. This is the only JNIEnv pointer that should be used for calling Java methods from a native method.

  • Caching and using an old saved JNIEnv pointer could in the best scenario crash your application. In worse scenarios, it might cause low-level inconsistency problems that are very hard to debug and understand.


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.