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 =>> %dn", 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] %dn", tid); JavaVMAttachArgs* args; args->version = JNI_VERSION_1_8; args->name = nullptr; args->group = nullptr; JNIEnv* lEnv; printf("[Attach for] %dn", tid); int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(lEnv), &args); printf("[Attach Done] %d =>> %dn", 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] %dn", tid); JavaVMAttachArgs* args; args->version = JNI_VERSION_1_8; args->name = nullptr; args->group = nullptr; JNIEnv* lEnv; printf("[GetEnv for] %dn", tid); int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(lEnv), JNI_VERSION_1_8); printf("[GetEnv Done] %d =>> %dn", tid, getEnvResult); printf("[Attach for] %dn", tid); int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(lEnv), &args); printf("[Attach Done] %d =>> %dn", 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] %dn", tid); int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(gEnv), JNI_VERSION_1_8); printf("[GetEnv Done] %d =>> %dn", tid, getEnvResult); printf("[Attach for] %dn", tid); int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(gEnv), &args); printf("[Attach Done] %d =>> %dn", 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] %dn", tid); auto* args = new JavaVMAttachArgs{}; args->version = JNI_VERSION_1_8; args->name = nullptr; args->group = nullptr; JNIEnv* lEnv; printf("[GetEnv for] %dn", tid); int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(&args, JNI_VERSION_1_8); printf("[GetEnv Done] %d =>> %dn", tid, getEnvResult); if (getEnvResult == JNI_EDETACHED) { printf("[Attach for] %dn", tid); int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(&lEnv), &args); printf("[Attach Done] %d =>> %dn", 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]
Advertisement
Answer
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] %dn", 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.