我是 JNI 和 C++ 新手。我有一些 API 需要与某些处理程序共享指针来订阅某些消息。我可以在“主”C++ 方法中调用处理程序中所需的方法,但是当我从 C++ 包装器调用它时,我收到 JVM 错误并且应用程序崩溃。我的本机方法是下一个:
public native int subscribe(Handler handler);
Java 处理程序类:
public class Handler {
public void call(String m1, String m2) {
System.out.println("call: " + m1 + " " + m2);
}
}
JNI 实现:
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);
};
一切正常,我用这段代码调用“call”方法。但我需要在订阅消息后调用这个方法,即对象会调用它。我为我的 java 类编写 C++ 包装器,将其传递给订阅方法:
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);
};
};
当主题调用“call”方法时,我收到 JVM 错误:
Java 运行时环境检测到致命错误:
SIGSEGV(0xb)位于 pc=0x7694d8a4,pid=5681,tid=5702
JRE版本:OpenJDK运行时环境(Zulu11.31+16-CA)(11.0.3+7)(build 11.0.3+7-LTS) Java虚拟机:OpenJDK客户端虚拟机(11.0.3+7-LTS,混合模式,串行GC,linux-arm) 有问题的框架: V [libjvm.so+0x3e58a4] get_method_id(JNIEnv_, _jclass, char const*, char const*, bool, Thread*) >>[克隆.isra.149]+0x288
出了什么问题?
只是要扩展@PaulMcKenzie 的评论。
您需要更换:
JavaWrapperHandler javaWrapperHandler = JavaWrapperHandler(env, javaHandler);
std::shared_ptr<JavaWrapperHandler> handlerSharedPointer = std::make_shared<JavaWrapperHandler>(javaWrapperHandler);
与
std::shared_ptr<JaveWrapperHandler> handlerSharedPointer = std::make_shared<JavaWrapperHandler>(env, javaHandler);
您违反了 JavaWrapperHandler 定义中的三规则,但您可以跳过修复该问题(因为使用全局引用修复它并不简单),只要您确保您的对象永远不会出现,除非通过指针引用。
最后,我编写了工作代码。在本机方法中,需要检索并保存 JVM 变量(可以在线程之间共享),以便在另一个线程中需要时检索 JNIenv(不能在线程之间共享):
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);
};
然后,在需要的地方检索 env。它还需要将当前线程附加到虚拟机:
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);
};
};