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

Tags: , , , ,



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:

Logic flow for what I'm doing

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]

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.



Source: stackoverflow