JNI 8 C++:线程附加和分离以及异步回调
JNI 8 C++ : Thread attach and detach And async callback
如何从 std::thread 异步调用 Java 方法?
我们假设这是一个 IM bot sdk,因为它的逻辑基本上是一个 IM bot sdk。
最重要的是:如何异步调用java方法和回调原生。
底部有逻辑流程,也许有帮助。
例如:
收到消息A“备份”,然后用MsgA调用java插件,插件处理这个事件需要10秒,并调用5次本地方法来满足它的需要。
同时,收到消息B“echo”,只需要10ms处理,并通过调用本地方法发送消息。
因此,MsgB 在 MsgA 之后接收,但在 MsgA 之前完成。
如果使用纯 C C++ java 或其他任何东西,那将很容易实现。但是在这里我发现了一个头疼的问题:JNI thread Attach.
※ 第一题:有线JNI attach
我已阅读文档找到答案,None 他们的工作和我的情况与每个人不同
我正在使用 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++ 中的辅助函数
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);
// 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)
并为 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)
更多 post 我发现,将本地替换为全局(这对逻辑和文档没有任何意义,但在他们的问题中已解决)
//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)
问题一:里面发生了什么?
※ 第二个问题:如何实现:
更新 1:
问题 1 已解决。
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();
}
没有转换会导致编译错误 error: invalid conversion from 'JNIEnv**' {aka 'JNIEnv_**'} to 'void**' [-fpermissive]
看来您的问题不在于 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)
,但它可能不是必需的。
如何从 std::thread 异步调用 Java 方法?
我们假设这是一个 IM bot sdk,因为它的逻辑基本上是一个 IM bot sdk。
最重要的是:如何异步调用java方法和回调原生。
底部有逻辑流程,也许有帮助。
例如:
收到消息A“备份”,然后用MsgA调用java插件,插件处理这个事件需要10秒,并调用5次本地方法来满足它的需要。
同时,收到消息B“echo”,只需要10ms处理,并通过调用本地方法发送消息。
因此,MsgB 在 MsgA 之后接收,但在 MsgA 之前完成。
如果使用纯 C C++ java 或其他任何东西,那将很容易实现。但是在这里我发现了一个头疼的问题:JNI thread Attach.
※ 第一题:有线JNI attach
我已阅读文档找到答案,None 他们的工作和我的情况与每个人不同
我正在使用 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++ 中的辅助函数
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);
// 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)
并为 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)
更多 post 我发现,将本地替换为全局(这对逻辑和文档没有任何意义,但在他们的问题中已解决)
//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)
问题一:里面发生了什么?
※ 第二个问题:如何实现:
更新 1:
问题 1 已解决。
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();
}
没有转换会导致编译错误 error: invalid conversion from 'JNIEnv**' {aka 'JNIEnv_**'} to 'void**' [-fpermissive]
看来您的问题不在于 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)
,但它可能不是必需的。