英文:
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++,演示代码如下:
public class ThreadTest {
private static int count = 0;
private static final Random random = new Random();
public static int count() {
try {
Thread.sleep(random.nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return count++;
}
}
以下是C++中的worker函数:
void worker(JNIEnv* localEnv) {
jclass clazz = localEnv->FindClass("ThreadTest");
jmethodID method = localEnv->GetStaticMethodID(clazz, "count", "()I");
jchar result = localEnv->CallStaticCharMethod(clazz, method);
int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
printf("[Worker Done] %d => %d\n", tid, result);
}
不附加情况下,我们会得到以下结果,这是预期的:
worker(env);
// 这里是主线程的第一次调用,正常工作;
// [Worker Done] -1444639049 => 0
jvm->DetachCurrentThread();
std::thread t1(worker, env);
t1.join();
// 进程崩溃,因为未附加JNI
// Process finished with exit code -1073741819 (0xC0000005)
然后为t1添加tWorker函数:
void tWorker(JavaVM* gJvm) {
int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
printf("[Thread Run] %d\n", tid);
JavaVMAttachArgs* args;
args->version = JNI_VERSION_1_8;
args->name = nullptr;
args->group = nullptr;
JNIEnv* lEnv;
printf("[Attach for] %d\n", tid);
int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(&lEnv), &args);
printf("[Attach Done] %d => %d\n", tid, attachResult);
delete args;
worker(lEnv);
gJvm->DetachCurrentThread();
}
我得到了这个结果:
[Worker Done] -1444639049 => 0
[Thread Run] 1709724944
Process finished with exit code -1073741819 (0xC0000005)
一些答案说你应该使用GetEnv
:
void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
printf("[Thread Run] %d\n", tid);
JavaVMAttachArgs* args;
args->version = JNI_VERSION_1_8;
args->name = nullptr;
args->group = nullptr;
JNIEnv* lEnv;
printf("[GetEnv for] %d\n", tid);
int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(&lEnv), JNI_VERSION_1_8);
printf("[GetEnv Done] %d => %d\n", tid, getEnvResult);
printf("[Attach for] %d\n", tid);
int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(&lEnv), &args);
printf("[Attach Done] %d => %d\n", tid, attachResult);
delete args;
worker(gEnv);
gJvm->DetachCurrentThread();
}
得到相同的结果:
[Worker Done] -1444639049 => 0
[Thread Run] 1709724944
Process finished with exit code -1073741819 (0xC0000005)
在更多的帖子中,有人建议将局部变量替换为全局变量(这在逻辑和文档上都没有意义,但在他们的问题中问题得到了解决):
//JNIEnv* lEnv;
printf("[GetEnv for] %d\n", tid);
int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(&gEnv), JNI_VERSION_1_8);
printf("[GetEnv Done] %d => %d\n", tid, getEnvResult);
printf("[Attach for] %d\n", tid);
int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(&gEnv), &args);
printf("[Attach Done] %d => %d\n", tid, attachResult);
这是无效的,即使我尝试了所有16种组合,也不适用于我的情况。
[Worker Done] -1444639049 => 0
[Thread Run] 1709724944
Process finished with exit code -1073741819 (0xC0000005)
问题一:这是怎么回事?
※ 第二个问题:如何实现这一点:
(以下内容中未提供翻译,请参考原文进行理解)
Update 1:
问题1已解决。
void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
printf("[Thread Run
<details>
<summary>英文:</summary>
How to async call Java method from std::thread ?
Let's assuming this is a IM bot sdk, Because it's logic basicly a IM bot sdk.
The most importtant is: How to async call java method and callback native.
There is logic flow at the bottom, Maybe helpful.
For example:
Receive message A "backup", 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.
Mean while, Receive message B "echo", That only take 10ms to process, And send an message by invoke native method.
So, MsgB recived after MsgA, But finish befor MsgA.
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.
※ First question: Wired JNI attach
I have read doc find answer, None of them working and my condition different with everyone
I'm using Zulu JDK8 (zulu8.48.0.53-ca-fx-jdk8.0.265-win_x64) and MinGW64 C++, For demo:
```java
public class ThreadTest {
private static int count = 0;
private static final Random random = new Random();
public static int count() {
try {
Thread.sleep(random.nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return count++;
}
}
Here is the worker function in C++
void worker(JNIEnv* localEnv) {
jclass clazz = localEnv->FindClass("ThreadTest");
jmethodID method = localEnv->GetStaticMethodID(clazz, "count", "()I");
jchar result = localEnv->CallStaticCharMethod(clazz, method);
int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
printf("[Worker Done] %d =>> %d\n", tid, result);
}
And without attach we will get, That is expected:
worker(env);
// Here the first call from main thread, Working find;
// [Worker Done] -1444639049 =>> 0
jvm->DetachCurrentThread();
std::thread t1(worker, env);
t1.join();
// Process crashed because not attach jni
// Process finished with exit code -1073741819 (0xC0000005)
And add the tWorker function for t1:
void tWorker (JavaVM* gJvm) {
int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
printf("[Thread Run] %d\n", tid);
JavaVMAttachArgs* args;
args->version = JNI_VERSION_1_8;
args->name = nullptr;
args->group = nullptr;
JNIEnv* lEnv;
printf("[Attach for] %d\n", tid);
int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(lEnv), &args);
printf("[Attach Done] %d =>> %d\n", tid, attachResult);
delete args;
worker(lEnv);
gJvm->DetachCurrentThread();
}
I got this:
[Worker Done] -1444639049 =>> 0
[Thread Run] 1709724944
Process finished with exit code -1073741819 (0xC0000005)
Some answer say you should use GetEnv
:
void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
printf("[Thread Run] %d\n", tid);
JavaVMAttachArgs* args;
args->version = JNI_VERSION_1_8;
args->name = nullptr;
args->group = nullptr;
JNIEnv* lEnv;
printf("[GetEnv for] %d\n", tid);
int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(lEnv), JNI_VERSION_1_8);
printf("[GetEnv Done] %d =>> %d\n", tid, getEnvResult);
printf("[Attach for] %d\n", tid);
int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(lEnv), &args);
printf("[Attach Done] %d =>> %d\n", tid, attachResult);
delete args;
worker(gEnv);
gJvm->DetachCurrentThread();
}
Got same result:
[Worker Done] -1444639049 =>> 0
[Thread Run] 1709724944
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)
//JNIEnv* lEnv;
printf("[GetEnv for] %d\n", tid);
int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(gEnv), JNI_VERSION_1_8);
printf("[GetEnv Done] %d =>> %d\n", tid, getEnvResult);
printf("[Attach for] %d\n", tid);
int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(gEnv), &args);
printf("[Attach Done] %d =>> %d\n", tid, attachResult);
That is usless, Even I try all 16 combination, That not work for me.
[Worker Done] -1444639049 =>> 0
[Thread Run] 1709724944
Process finished with exit code -1073741819 (0xC0000005)
Question one: What happen in there?
※ Second Question: How to achive that:
Update 1:
Question 1 solved.
void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
printf("[Thread Run] %d\n", tid);
auto* args = new JavaVMAttachArgs{};
args->version = JNI_VERSION_1_8;
args->name = nullptr;
args->group = nullptr;
JNIEnv* lEnv;
printf("[GetEnv for] %d\n", tid);
int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(&args, JNI_VERSION_1_8);
printf("[GetEnv Done] %d =>> %d\n", tid, getEnvResult);
if (getEnvResult == JNI_EDETACHED) {
printf("[Attach for] %d\n", tid);
int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(&lEnv), &args);
printf("[Attach Done] %d =>> %d\n", tid, attachResult);
}
delete args;
worker(gEnv);
gJvm->DetachCurrentThread();
}
Without cast will cause a complie error error: invalid conversion from 'JNIEnv**' {aka 'JNIEnv_**'} to 'void**' [-fpermissive]
答案1
得分: 2
看起来你的问题不在于JVM的使用,而在于C++代码。看一下这段代码:
void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
printf("[Thread Run] %d\n", tid);
JavaVMAttachArgs* args;
args->version = JNI_VERSION_1_8;
args->name = nullptr;
args->group = nullptr;
注意这里:
JavaVMAttachArgs* args;
args->version = JNI_VERSION_1_8;
你的args是一个指针,并且没有被初始化。这会引发未定义的行为,很可能会导致崩溃。
而且你尝试删除了未初始化的指针:
delete args;
此外,我不理解这段代码的用意:
JNIEnv* lEnv;
...
int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(lEnv), ...
在这里使用reinterpret_cast的意义是什么?根据函数的定义,需要传递一个指向指针的指针,而不是一个转换:
JNIEnv* lEnv;
...
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:
void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {
int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
printf("[Thread Run] %d\n", tid);
JavaVMAttachArgs* args;
args->version = JNI_VERSION_1_8;
args->name = nullptr;
args->group = nullptr;
Pay attention here:
JavaVMAttachArgs* args;
args->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:
delete args;
Also I don't understand this piece of code:
JNIEnv* lEnv;
...
int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(lEnv), ...
What is the sense of reinterpret_cast here? By definition of the function there is required a pointer to pointer, not a cast:
JNIEnv* lEnv;
...
int getEnvResult = gJvm->GetEnv(&lEnv, ...
Ok, you can cast it, but you should pass pointer to pointer here, so cast a pointer to pointer static_cast<void**>(&lEnv)
, but it is probably not required.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论