问题
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.
回答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.
回答2:
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);
};
};
来源:https://stackoverflow.com/questions/63016784/cant-call-java-methods-from-c-wrapper-in-jni