2

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

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, I call 'call' method with this code. But I need to call this method after I subscribe to messages, I.e. Subject will call it. I write c++ wrapper for my java class to pass it to 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 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? Thanks in advance.

Valeriy K.
  • 2,616
  • 1
  • 30
  • 53
  • 1
    Please investigate this: `JavaWrapperHandler javaWrapperHandler = JavaWrapperHandler(env, javaHandler);` -- Your `JavaWrapperHandler` class does not follow the [rule of 3](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three/4172961). Put a breakpoint in the destructor in `JavaWrapperHandler`, and ensure the destructor isn't being called when that assignment is done. Basically any C++ class that has a user-defined destructor like that had better have a user-defined copy constructor and assignment operator, otherwise it is a bug trap waiting to happen. – PaulMcKenzie Jul 21 '20 at 14:39
  • 1
    Which threads are these calls happening on? You can't share a `JNIEnv*` across different threads. See https://stackoverflow.com/questions/30026030/what-is-the-best-way-to-save-jnienv/30026231#30026231 – Michael Jul 21 '20 at 15:26
  • @Michael Thanks, I'll try – Valeriy K. Jul 21 '20 at 16:00
  • @Michael As I understand vm variavle is already defined. How I can obtain vm? – Valeriy K. Jul 22 '20 at 06:49
  • It is a some mysterious for me - I saw many code samples how to get JNIenv with vm, but for some reason, vm is already existed and no one gave code to get vm – Valeriy K. Jul 22 '20 at 06:56
  • As I said in my earlier question, you can either call `GetJavaVM` on an existing `JNIEnv` pointer OR the `JNI_GetCreatedJavaVMs` function globally. – Botje Jul 22 '20 at 07:23
  • 2
    Personally I save the `JavaVM*` I receive in `JNI_OnLoad` in a global variable. Unlike a `JNIEnv*`, sharing a `JavaVM*` across threads is not a problem. – Michael Jul 22 '20 at 07:24

2 Answers2

1

Just going to expand on @PaulMcKenzie 's comment.

You need to replace:

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

with

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

You are violating the rule of three in your definition of JavaWrapperHandler, but you can skip fixing that (since fixing it isn't straightforward with the global reference) as long as you make sure your object never appears except through a pointer reference.

antlersoft
  • 14,636
  • 4
  • 35
  • 55
  • Thanks for your reply. I really don't understand what happens when I instantiate wrapper in my constructor or as you wrote. I know java and don't know c++. Anyway, that doesn't work either. I am getting the same error. But I have logs in destructor in my original code, and I see that with my code destructor is called just after the constructor. But now destructor doesn't call – Valeriy K. Jul 21 '20 at 15:23
  • In any case, you should be making sure (maybe with logging) that there is only one instance of JavaWrapperHandler created and the destructor isn't called before the call method is invoked (on any instance...) – antlersoft Jul 21 '20 at 17:30
0

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);

};
};
Valeriy K.
  • 2,616
  • 1
  • 30
  • 53