Reputation: 444
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:
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]
Upvotes: 1
Views: 952
Reputation: 2158
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.
Upvotes: 2