JNI 8 C++:线程附加与分离以及异步回调

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

JNI 8 C++ : Thread attach and detach And async callback

问题

如何从std::thread异步调用Java方法?

假设这是一个即时通讯机器人SDK,因为它的逻辑基本上是一个即时通讯机器人SDK。

最重要的是:如何异步调用Java方法并回调本地方法。

底部有逻辑流程,可能有帮助。

例如:

接收消息A "backup",然后使用MsgA调用Java插件,插件处理此事件需要10秒,并调用5次本地方法以满足其需求。

同时,接收消息B "echo",仅需要10毫秒处理,并通过调用本地方法发送消息。

因此,MsgB在MsgA之后接收,但在MsgA之前完成。

如果只使用纯C、C++、Java或其他任何语言,实现起来将非常简单。但我在这里遇到了一个头痛的问题:JNI线程附加。

※ 第一个问题:奇怪的JNI附加

我阅读了文档找到了答案,但其中没有一个适用于我的情况。

我正在使用Zulu JDK8(zulu8.48.0.53-ca-fx-jdk8.0.265-win_x64)和MinGW64 C++,演示代码如下:

  1. public class ThreadTest {
  2. private static int count = 0;
  3. private static final Random random = new Random();
  4. public static int count() {
  5. try {
  6. Thread.sleep(random.nextInt(2000));
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. return count++;
  11. }
  12. }

以下是C++中的worker函数:

  1. void worker(JNIEnv* localEnv) {
  2. jclass clazz = localEnv->FindClass("ThreadTest");
  3. jmethodID method = localEnv->GetStaticMethodID(clazz, "count", "()I");
  4. jchar result = localEnv->CallStaticCharMethod(clazz, method);
  5. int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
  6. printf("[Worker Done] %d => %d\n", tid, result);
  7. }

不附加情况下,我们会得到以下结果,这是预期的:

  1. worker(env);
  2. // 这里是主线程的第一次调用,正常工作;
  3. // [Worker Done] -1444639049 => 0
  4. jvm->DetachCurrentThread();
  5. std::thread t1(worker, env);
  6. t1.join();
  7. // 进程崩溃,因为未附加JNI
  8. // Process finished with exit code -1073741819 (0xC0000005)

然后为t1添加tWorker函数:

  1. void tWorker(JavaVM* gJvm) {
  2. int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
  3. printf("[Thread Run] %d\n", tid);
  4. JavaVMAttachArgs* args;
  5. args->version = JNI_VERSION_1_8;
  6. args->name = nullptr;
  7. args->group = nullptr;
  8. JNIEnv* lEnv;
  9. printf("[Attach for] %d\n", tid);
  10. int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(&lEnv), &args);
  11. printf("[Attach Done] %d => %d\n", tid, attachResult);
  12. delete args;
  13. worker(lEnv);
  14. gJvm->DetachCurrentThread();
  15. }

我得到了这个结果:

  1. [Worker Done] -1444639049 => 0
  2. [Thread Run] 1709724944
  3. Process finished with exit code -1073741819 (0xC0000005)

一些答案说你应该使用GetEnv

  1. void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
  2. int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
  3. printf("[Thread Run] %d\n", tid);
  4. JavaVMAttachArgs* args;
  5. args->version = JNI_VERSION_1_8;
  6. args->name = nullptr;
  7. args->group = nullptr;
  8. JNIEnv* lEnv;
  9. printf("[GetEnv for] %d\n", tid);
  10. int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(&lEnv), JNI_VERSION_1_8);
  11. printf("[GetEnv Done] %d => %d\n", tid, getEnvResult);
  12. printf("[Attach for] %d\n", tid);
  13. int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(&lEnv), &args);
  14. printf("[Attach Done] %d => %d\n", tid, attachResult);
  15. delete args;
  16. worker(gEnv);
  17. gJvm->DetachCurrentThread();
  18. }

得到相同的结果:

  1. [Worker Done] -1444639049 => 0
  2. [Thread Run] 1709724944
  3. Process finished with exit code -1073741819 (0xC0000005)

在更多的帖子中,有人建议将局部变量替换为全局变量(这在逻辑和文档上都没有意义,但在他们的问题中问题得到了解决):

  1. //JNIEnv* lEnv;
  2. printf("[GetEnv for] %d\n", tid);
  3. int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(&gEnv), JNI_VERSION_1_8);
  4. printf("[GetEnv Done] %d => %d\n", tid, getEnvResult);
  5. printf("[Attach for] %d\n", tid);
  6. int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(&gEnv), &args);
  7. printf("[Attach Done] %d => %d\n", tid, attachResult);

这是无效的,即使我尝试了所有16种组合,也不适用于我的情况。

  1. [Worker Done] -1444639049 => 0
  2. [Thread Run] 1709724944
  3. Process finished with exit code -1073741819 (0xC0000005)

问题一:这是怎么回事?

※ 第二个问题:如何实现这一点:

(以下内容中未提供翻译,请参考原文进行理解)

JNI 8 C++:线程附加与分离以及异步回调

Update 1:

问题1已解决。

  1. void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
  2. int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
  3. printf("[Thread Run
  4. <details>
  5. <summary>英文:</summary>
  6. How to async call Java method from std::thread ?
  7. Let&#39;s assuming this is a IM bot sdk, Because it&#39;s logic basicly a IM bot sdk.
  8. The most importtant is: How to async call java method and callback native.
  9. There is logic flow at the bottom, Maybe helpful.
  10. For example:
  11. Receive message A &quot;backup&quot;, Then call the java plugin with MsgA, The plugin porcess this event need 10second, And call 5 time native method for what ever it need.
  12. Mean while, Receive message B &quot;echo&quot;, That only take 10ms to process, And send an message by invoke native method.
  13. So, MsgB recived after MsgA, But finish befor MsgA.
  14. If using pure C C++ java or what ever, That will be so easy to achive. But here I found a headache problem: JNI thread Attach.
  15. First question: Wired JNI attach
  16. I have read doc find answer, None of them working and my condition different with everyone
  17. I&#39;m using Zulu JDK8 (zulu8.48.0.53-ca-fx-jdk8.0.265-win_x64) and MinGW64 C++, For demo:
  18. ```java
  19. public class ThreadTest {
  20. private static int count = 0;
  21. private static final Random random = new Random();
  22. public static int count() {
  23. try {
  24. Thread.sleep(random.nextInt(2000));
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. return count++;
  29. }
  30. }

Here is the worker function in C++

  1. void worker(JNIEnv* localEnv) {
  2. jclass clazz = localEnv-&gt;FindClass(&quot;ThreadTest&quot;);
  3. jmethodID method = localEnv-&gt;GetStaticMethodID(clazz, &quot;count&quot;, &quot;()I&quot;);
  4. jchar result = localEnv-&gt;CallStaticCharMethod(clazz, method);
  5. int tid = std::hash&lt;std::thread::id&gt;{}(std::this_thread::get_id());
  6. printf(&quot;[Worker Done] %d =&gt;&gt; %d\n&quot;, tid, result);
  7. }

And without attach we will get, That is expected:

  1. worker(env);
  2. // Here the first call from main thread, Working find;
  3. // [Worker Done] -1444639049 =&gt;&gt; 0
  4. jvm-&gt;DetachCurrentThread();
  5. std::thread t1(worker, env);
  6. t1.join();
  7. // Process crashed because not attach jni
  8. // Process finished with exit code -1073741819 (0xC0000005)

And add the tWorker function for t1:

  1. void tWorker (JavaVM* gJvm) {
  2. int tid = std::hash&lt;std::thread::id&gt;{}(std::this_thread::get_id());
  3. printf(&quot;[Thread Run] %d\n&quot;, tid);
  4. JavaVMAttachArgs* args;
  5. args-&gt;version = JNI_VERSION_1_8;
  6. args-&gt;name = nullptr;
  7. args-&gt;group = nullptr;
  8. JNIEnv* lEnv;
  9. printf(&quot;[Attach for] %d\n&quot;, tid);
  10. int attachResult = gJvm-&gt;AttachCurrentThread(reinterpret_cast&lt;void**&gt;(lEnv), &amp;args);
  11. printf(&quot;[Attach Done] %d =&gt;&gt; %d\n&quot;, tid, attachResult);
  12. delete args;
  13. worker(lEnv);
  14. gJvm-&gt;DetachCurrentThread();
  15. }

I got this:

  1. [Worker Done] -1444639049 =&gt;&gt; 0
  2. [Thread Run] 1709724944
  3. Process finished with exit code -1073741819 (0xC0000005)

Some answer say you should use GetEnv:

  1. void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
  2. int tid = std::hash&lt;std::thread::id&gt;{}(std::this_thread::get_id());
  3. printf(&quot;[Thread Run] %d\n&quot;, tid);
  4. JavaVMAttachArgs* args;
  5. args-&gt;version = JNI_VERSION_1_8;
  6. args-&gt;name = nullptr;
  7. args-&gt;group = nullptr;
  8. JNIEnv* lEnv;
  9. printf(&quot;[GetEnv for] %d\n&quot;, tid);
  10. int getEnvResult = gJvm-&gt;GetEnv(reinterpret_cast&lt;void**&gt;(lEnv), JNI_VERSION_1_8);
  11. printf(&quot;[GetEnv Done] %d =&gt;&gt; %d\n&quot;, tid, getEnvResult);
  12. printf(&quot;[Attach for] %d\n&quot;, tid);
  13. int attachResult = gJvm-&gt;AttachCurrentThread(reinterpret_cast&lt;void**&gt;(lEnv), &amp;args);
  14. printf(&quot;[Attach Done] %d =&gt;&gt; %d\n&quot;, tid, attachResult);
  15. delete args;
  16. worker(gEnv);
  17. gJvm-&gt;DetachCurrentThread();
  18. }

Got same result:

  1. [Worker Done] -1444639049 =&gt;&gt; 0
  2. [Thread Run] 1709724944
  3. Process finished with exit code -1073741819 (0xC0000005)

For more post I found, Replace Local to Global (That dosen't make any sense for logic and Document but in their question problem solved)

  1. //JNIEnv* lEnv;
  2. printf(&quot;[GetEnv for] %d\n&quot;, tid);
  3. int getEnvResult = gJvm-&gt;GetEnv(reinterpret_cast&lt;void**&gt;(gEnv), JNI_VERSION_1_8);
  4. printf(&quot;[GetEnv Done] %d =&gt;&gt; %d\n&quot;, tid, getEnvResult);
  5. printf(&quot;[Attach for] %d\n&quot;, tid);
  6. int attachResult = gJvm-&gt;AttachCurrentThread(reinterpret_cast&lt;void**&gt;(gEnv), &amp;args);
  7. printf(&quot;[Attach Done] %d =&gt;&gt; %d\n&quot;, tid, attachResult);

That is usless, Even I try all 16 combination, That not work for me.

  1. [Worker Done] -1444639049 =&gt;&gt; 0
  2. [Thread Run] 1709724944
  3. Process finished with exit code -1073741819 (0xC0000005)

Question one: What happen in there?

※ Second Question: How to achive that:

JNI 8 C++:线程附加与分离以及异步回调

Update 1:

Question 1 solved.

  1. void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
  2. int tid = std::hash&lt;std::thread::id&gt;{}(std::this_thread::get_id());
  3. printf(&quot;[Thread Run] %d\n&quot;, tid);
  4. auto* args = new JavaVMAttachArgs{};
  5. args-&gt;version = JNI_VERSION_1_8;
  6. args-&gt;name = nullptr;
  7. args-&gt;group = nullptr;
  8. JNIEnv* lEnv;
  9. printf(&quot;[GetEnv for] %d\n&quot;, tid);
  10. int getEnvResult = gJvm-&gt;GetEnv(reinterpret_cast&lt;void**&gt;(&amp;args, JNI_VERSION_1_8);
  11. printf(&quot;[GetEnv Done] %d =&gt;&gt; %d\n&quot;, tid, getEnvResult);
  12. if (getEnvResult == JNI_EDETACHED) {
  13. printf(&quot;[Attach for] %d\n&quot;, tid);
  14. int attachResult = gJvm-&gt;AttachCurrentThread(reinterpret_cast&lt;void**&gt;(&amp;lEnv), &amp;args);
  15. printf(&quot;[Attach Done] %d =&gt;&gt; %d\n&quot;, tid, attachResult);
  16. }
  17. delete args;
  18. worker(gEnv);
  19. gJvm-&gt;DetachCurrentThread();
  20. }

Without cast will cause a complie error error: invalid conversion from &#39;JNIEnv**&#39; {aka &#39;JNIEnv_**&#39;} to &#39;void**&#39; [-fpermissive]

答案1

得分: 2

看起来你的问题不在于JVM的使用,而在于C++代码。看一下这段代码:

  1. void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
  2. int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
  3. printf("[Thread Run] %d\n", tid);
  4. JavaVMAttachArgs* args;
  5. args->version = JNI_VERSION_1_8;
  6. args->name = nullptr;
  7. args->group = nullptr;

注意这里:

  1. JavaVMAttachArgs* args;
  2. args->version = JNI_VERSION_1_8;

你的args是一个指针,并且没有被初始化。这会引发未定义的行为,很可能会导致崩溃。

而且你尝试删除了未初始化的指针:

  1. delete args;

此外,我不理解这段代码的用意:

  1. JNIEnv* lEnv;
  2. ...
  3. int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(lEnv), ...

在这里使用reinterpret_cast的意义是什么?根据函数的定义,需要传递一个指向指针的指针,而不是一个转换:

  1. JNIEnv* lEnv;
  2. ...
  3. int getEnvResult = gJvm->GetEnv(&lEnv, ...

好吧,你可以进行类型转换,但是在这里你应该传递一个指向指针的指针static_cast<void**>(&lEnv),但这可能并不是必需的。

英文:

Looks like the your problems are not in usage of JVM but in C++ code. Looking at this piece of code:

  1. void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
  2. int tid = std::hash&lt;std::thread::id&gt;{}(std::this_thread::get_id());
  3. printf(&quot;[Thread Run] %d\n&quot;, tid);
  4. JavaVMAttachArgs* args;
  5. args-&gt;version = JNI_VERSION_1_8;
  6. args-&gt;name = nullptr;
  7. args-&gt;group = nullptr;

Pay attention here:

  1. JavaVMAttachArgs* args;
  2. args-&gt;version = JNI_VERSION_1_8;

Your args is a pointer and is not initialized. It invokes undefined behavior, is most likely to crash.
Also you are trying to delete it uninitialized:

  1. delete args;

Also I don't understand this piece of code:

  1. JNIEnv* lEnv;
  2. ...
  3. int getEnvResult = gJvm-&gt;GetEnv(reinterpret_cast&lt;void**&gt;(lEnv), ...

What is the sense of reinterpret_cast here? By definition of the function there is required a pointer to pointer, not a cast:

  1. JNIEnv* lEnv;
  2. ...
  3. int getEnvResult = gJvm-&gt;GetEnv(&amp;lEnv, ...

Ok, you can cast it, but you should pass pointer to pointer here, so cast a pointer to pointer static_cast&lt;void**&gt;(&amp;lEnv), but it is probably not required.

huangapple
  • 本文由 发表于 2020年8月21日 11:08:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/63515957.html
匿名

发表评论

匿名网友

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

确定