Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 212
SomethingSomething Avatar asked Sep 08 '26 07:09

SomethingSomething


2 Answers

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.

like image 104
Jacob G. Avatar answered Sep 10 '26 19:09

Jacob G.


Instead of creating a new Java VM (which isn't possible anyway when the current process already has one) you can get the existing Java VM with JNI_GetCreatedJavaVMs (https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNI_GetCreatedJavaVMs). Use AttachCurrentThread (https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#AttachCurrentThread) to get an env pointer.

This will also work in a multithreaded environment, while passing env pointers around will only work in a single thread.

like image 40
user2543253 Avatar answered Sep 10 '26 19:09

user2543253



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!