无法从 JNI 中的 C++ 包装器调用 java 方法

Can't call java methods from c++ wrapper in JNI

我是 JNI 和 C++ 的新手。我有一些 api 需要与某些处理程序共享指针来订阅某些消息。我可以在“main”c++ 方法中调用我的处理程序中的必需方法,但是当我从 c++ 包装器调用它时,我得到 JVM 错误并且我的应用程序崩溃。接下来是我的原生方法:

public native int subscribe(Handler handler);

Java 处理程序 class:

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 class 编写了 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);

};
};

当 Subject 调用 'call' 方法时,我收到 JVM 错误:

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

怎么了?提前致谢。

只是想扩展@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);
};

然后,在需要的地方检索环境。它还需要将当前线程附加到 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);

};
};