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

huangapple go评论86阅读模式
英文:

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

问题

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:

我是JNI和C++的新手。我有一些需要使用带有处理程序的共享指针订阅一些消息的API。我可以在我的处理程序中调用所需的方法在“main”C++方法中,但是当我从C++包装器中调用它时,我会收到JVM错误并且我的应用程序会崩溃。我的本地方法如下:

public native int subscribe(Handler handler);

Java Handler class:

Java处理程序类:

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

JNI implementation:

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

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:

一切都运行正常,我使用这段代码调用'call'方法。但是我需要在订阅消息后调用这个方法,即主题将调用它。我为我的Java类编写了C++包装器,以将其传递给subscribe方法:

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:

当主题调用'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

What is wrong? Thanks in advance.

发生了什么问题?提前感谢您。

英文:

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(&quot;call: &quot; + m1 + &quot; &quot; + m2);
}

}

JNI implementation:

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

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

JavaWrapperHandler javaWrapperHandler = JavaWrapperHandler(env, javaHandler);
std::shared_ptr&lt;JavaWrapperHandler&gt; handlerSharedPointer = std::make_shared&lt;JavaWrapperHandler&gt;(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-&gt;NewGlobalRef(handler);
    }

~JavaWrapperHandler() {
        env-&gt;DeleteGlobalRef(javaHandler);
}

virtual void call(const std::string &amp;message1, const std::string &amp;message2) {
	jclass handlerClass = env-&gt;GetObjectClass(javaHandler);
    jmethodID call = env-&gt;GetMethodID(handlerClass, &quot;call&quot;, &quot;(Ljava/lang/String;Ljava/lang/String;)V&quot;);  // Here I get error
    jstring javMessage1 = env-&gt;NewStringUTF((const char* )message1.c_str());
    jstring javMessage2 = env-&gt;NewStringUTF((const char* )message2.c_str());
    env-&gt;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

得分: 1

需要替换:

JavaWrapperHandler javaWrapperHandler = JavaWrapperHandler(env, javaHandler);
std::shared_ptr&lt;JavaWrapperHandler&gt; handlerSharedPointer = std::make_shared&lt;JavaWrapperHandler&gt;(javaWrapperHandler);

替换为:

std::shared_ptr&lt;JavaWrapperHandler&gt; handlerSharedPointer = std::make_shared&lt;JavaWrapperHandler&gt;(env, javaHandler);

在定义JavaWrapperHandler时,你违反了"三大法则"(Rule of Three),但只要确保对象只通过指针引用出现,你可以跳过修复它(因为使用全局引用修复它不直接)。

英文:

Just going to expand on @PaulMcKenzie 's comment.

You need to replace:

JavaWrapperHandler javaWrapperHandler = JavaWrapperHandler(env, javaHandler);
std::shared_ptr&lt;JavaWrapperHandler&gt; handlerSharedPointer = std::make_shared&lt;JavaWrapperHandler&gt;(javaWrapperHandler);

with

std::shared_ptr&lt;JaveWrapperHandler&gt; handlerSharedPointer = std::make_shared&lt;JavaWrapperHandler&gt;(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

得分: 0

以下是您要求的代码部分的翻译:

最后,我编写了可工作的代码。在本地方法中,需要检索和保存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 << "无法获取JavaVm实例" << 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 << "线程分离。" << std::endl;
            if (vm->AttachCurrentThread((void**)&env, NULL) == JNI_OK) {
                std::cout << "附加当前线程到虚拟机" << std::endl;
            } else {
                std::cout << "无法附加线程。" << std::endl;
            }
        } else if (result == JNI_EVERSION) {
            std::cout << "不支持的JNI版本。" << std::endl;
        }

        jclass handlerClass = env->GetObjectClass(javaHandler);
        jmethodID call = env->GetMethodID(handlerClass, "call", "(Ljava/lang/String;Ljava/lang/String;)V");  // 在这里我遇到错误
        jstring javMessage1 = env->NewStringUTF((const char*)message1.c_str());
        jstring javMessage2 = env->NewStringUTF((const char*)message2.c_str());
        env->CallVoidMethod(javaHandler, call, javMessage1, javMessage2);
    };
};

希望这能帮助您理解代码的翻译。如果您有任何其他问题,请随时提出。

英文:

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-&gt;GetJavaVM(&amp;jvm);
if(status != 0) {
std::cout &lt;&lt; &quot;Failed to receive JavaVm instance&quot; &lt;&lt; std::endl;
}
std::shared_ptr&lt;JavaWrapperHandler&gt; handlerSharedPointer =
std::make_shared&lt;JavaWrapperHandler&gt;(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-&gt;GetEnv((void**)&amp;env, JNI_VERSION_1_6);
javaHandler = env-&gt;NewGlobalRef(handler);
}
~JavaWrapperHandler() {}
virtual void call(const std::string &amp;message1, const std::string &amp;message2) {
JNIEnv *env = nullptr;
auto result = vm-&gt;GetEnv((void**)&amp;env, JNI_VERSION_1_6);
if (result == JNI_EDETACHED) {
std::cout &lt;&lt; &quot;Thread detached.&quot; &lt;&lt; std::endl;
if (vm-&gt;AttachCurrentThread((void**)&amp;env, NULL) == JNI_OK) {
std::cout &lt;&lt; &quot;Attach current thread to vm&quot; &lt;&lt; std::endl;
} else {
std::cout &lt;&lt; &quot;Failed to attach thread.&quot; &lt;&lt; std::endl;
}
} else if (result == JNI_EVERSION) {
std::cout &lt;&lt; &quot;Unsupported JNI version.&quot; &lt;&lt; std::endl;
}
jclass handlerClass = env-&gt;GetObjectClass(javaHandler);
jmethodID call = env-&gt;GetMethodID(handlerClass, &quot;call&quot;, &quot;(Ljava/lang/String;Ljava/lang/String;)V&quot;);  // Here I get error
jstring javMessage1 = env-&gt;NewStringUTF((const char* )message1.c_str());
jstring javMessage2 = env-&gt;NewStringUTF((const char* )message2.c_str());
env-&gt;CallVoidMethod(javaHandler, call, javMessage1, javMessage2);
};
};

huangapple
  • 本文由 发表于 2020年7月21日 22:28:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/63016784.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定