javac++pointersjava-native-interface

Can't call Java methods from a C++ wrapper in JNI


I'm new to JNI and C++. I have some API that required a shared pointer with some handler to subscribe on some messages. I can call the required method in my handler in the "main" C++ method, but when I call it from a C++ wrapper, I get a JVM error and my application crashes. My native method is the following:

public native int subscribe(Handler handler);

Java Handler class:

public class Handler {
public void call(String m1, String m2) {
    System.out.println("call: " + m1 + " " + m2);
}

}

JNI implementation:

JNIEXPORT jint JNICALL Java_com_lib_NativeClient_subscribe (JNIEnv* env, jobject thisObj, jobject javaHandler) {

jclass handlerClass = env->GetObjectClass(javaHandler);
jmethodID call = env->GetMethodID(handlerClass, "call", "(Ljava/lang/String;Ljava/lang/String;)V");
const std::string &message1 = "message1";
const std::string &message2 = "message2";
jstring javMessage1 = env->NewStringUTF((const char* )message1.c_str());
jstring javMessage2 = env->NewStrbingUTF((const char* )message2.c_str());
env->CallVoidMethod(javaHandler, call, javMessage1, javMessage2);

JavaWrapperHandler javaWrapperHandler = JavaWrapperHandler(env, javaHandler);
std::shared_ptr<JavaWrapperHandler> handlerSharedPointer = std::make_shared<JavaWrapperHandler>(javaWrapperHandler);

return some::lib::subscribe(handlerSharedPointer);
};

All works fine, and I call the 'call' method with this code. But I need to call this method after I subscribe to messages, i.e. Subject will call it. I wrote a C++ wrapper for my java class to pass it to the subscribe method:

class JavaWrapperHandler : public some::lib::Handler {
JNIEnv* env;
jobject javaHandler;
public:
JavaWrapperHandler(JNIEnv* genEnv, jobject handler) {
        env = genEnv;
       javaHandler = env->NewGlobalRef(handler);
    }

~JavaWrapperHandler() {
        env->DeleteGlobalRef(javaHandler);
}

virtual void call(const std::string &message1, const std::string &message2) {
    jclass handlerClass = env->GetObjectClass(javaHandler);
    jmethodID call = env->GetMethodID(handlerClass, "call", "(Ljava/lang/String;Ljava/lang/String;)V");  // Here I get error
    jstring javMessage1 = env->NewStringUTF((const char* )message1.c_str());
    jstring javMessage2 = env->NewStringUTF((const char* )message2.c_str());
    env->CallVoidMethod(javaHandler, call, javMessage1, javMessage2);

};
};

When Subject call 'call' method I receive a JVM error:

A fatal error has been detected by the Java Runtime Environment:

SIGSEGV (0xb) at pc=0x7694d8a4, pid=5681, tid=5702

JRE version: OpenJDK Runtime Environment (Zulu11.31+16-CA) (11.0.3+7) (build 11.0.3+7-LTS) Java VM: OpenJDK Client VM (11.0.3+7-LTS, mixed mode, serial gc, linux-arm) Problematic frame: V [libjvm.so+0x3e58a4] get_method_id(JNIEnv_, _jclass, char const*, char const*, bool, Thread*) >>[clone .isra.149]+0x288

What is wrong?


Solution

  • Finally, I wrote working code. In the native method it is required to retrieve and save JVM variable (which can be shared between threads) to retrieve JNIenv (which can't be shared between threads) when it will required in another thread:

    JNIEXPORT jint JNICALL Java_com_lib_NativeClient_subscribe (JNIEnv* env, jobject thisObj, jobject javaHandler) {
    static JavaVM *jvm;
    int status = env->GetJavaVM(&jvm);
        if(status != 0) {
            std::cout << "Failed to receive JavaVm instance" << std::endl;
        }
    std::shared_ptr<JavaWrapperHandler> handlerSharedPointer =
    std::make_shared<JavaWrapperHandler>(jvm, javaWrapperHandler);
    return some::lib::subscribe(handlerSharedPointer);
    };
    

    Then, retrieve env where it is required. Also it required to attach current thread to vm:

        class JavaWrapperHandler : public some::lib::Handler {
    JavaVM *vm;
    jobject javaHandler;
    public:
    JavaWrapperHandler(JavaVM *gen_vm, jobject handler) {
           vm = gen_jvm;
           JNIEnv *env = nullptr;
           vm->GetEnv((void**)&env, JNI_VERSION_1_6);
           javaHandler = env->NewGlobalRef(handler);
        }
    
    ~JavaWrapperHandler() {}
    
    virtual void call(const std::string &message1, const std::string &message2) {
            JNIEnv *env = nullptr;
            auto result = vm->GetEnv((void**)&env, JNI_VERSION_1_6);
            if (result == JNI_EDETACHED) {
                    std::cout << "Thread detached." << std::endl;
                    if (vm->AttachCurrentThread((void**)&env, NULL) == JNI_OK) {
                        std::cout << "Attach current thread to vm" << std::endl;
                    } else {
                        std::cout << "Failed to attach thread." << std::endl;
                    }
                } else if (result == JNI_EVERSION) {
                std::cout << "Unsupported JNI version." << std::endl;
                }
    
        jclass handlerClass = env->GetObjectClass(javaHandler);
        jmethodID call = env->GetMethodID(handlerClass, "call", "(Ljava/lang/String;Ljava/lang/String;)V");  // Here I get error
        jstring javMessage1 = env->NewStringUTF((const char* )message1.c_str());
        jstring javMessage2 = env->NewStringUTF((const char* )message2.c_str());
        env->CallVoidMethod(javaHandler, call, javMessage1, javMessage2);
    
    };
    };