NDK learning notes: thread JNIEnv, JavaVM, JNI_OnLoad (GetEnv returns NULL? FindClass returns NULL?)

Hello, everyone. Meet again. I'm your friend Quan Zhanjun.

NDK learning notes: thread JNIEnv, JavaVM, JNI_OnLoad

This article is the second theoretical knowledge note about NDK threads. There are two main points, as follows:

  1. pthread_create(Too many arguements, expected 1) ?
  2. How to get JNIEnv in the thread? GetEnv returns NULL?
  3. FindClass returns NULL?

First, the code of MainActivity on the homepage is as follows:

public class MainActivity extends Activity {

    static {
        try {
            System.loadLibrary("native-lib");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public native void nativeThreadEnvTest();

    public static String getUuid() {
        // Provided to nativeThreadEnvTest for use
        return UUID.randomUUID().toString();
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) ...
}
copy

nativeThreadEnvTest intends to realize such a function: the for loop calls the MainActivity.getUuid method to print out five different UUID s. It sounds simple, and the logic code is as follows:

#include <unistd.h>
#include <pthread.h>
#include <assert.h>

void *pthread_run(void *arg) {
    JNIEnv *env = NULL;
    // get env ?
    char *name = (char *) arg;
    for (int i = 0; i < 5; ++i) {
        //char* uuid_cstr = ...
        LOGI("%s, No:%d, uuid:%s", name, i, uuid_cstr);
        sleep(1);
    }

    pthread_exit((void *) 0);
}

JNIEXPORT void JNICALL
Java_org_zzrblog_MainActivity_nativeThreadEnvTest(JNIEnv *env, jobject thiz) {
    pthread_t tid;
    pthread_create(&tid, NULL, pthread_run, (void *) "pthread1");

    //void* reval;
    //pthread_join(tid, &reval);
}
copy

At this time, the first pit may appear: pthread_create gives an error prompt too many arguments, expected 1 (black three question mark)

ctrl+ left click to jump to the definition of the header file pthread.h, which clearly has four parameters?

rebuild, restart AS, and all kinds of methods have not been solved. What should I do? Here is a feasible and scientific solution. Add the following macro definition to the header file to OJBK!

#ifndef _PTHREAD_H_
#define _PTHREAD_H_

#include ...
// Add macro definition_ Nonnull
#ifndef _Nonnull
#define _Nonnull
#endif
copy

Then we continue to implement the function, and execute the function pthread in the thread_ In run, you want to call mainactivity Getuuid method must have env. So how do we get env? Maybe a big brother immediately said: NewGlobalRef when the env passed in by nativeThreadEnvTest, so it can be used globally! This seems to be a solution, which seems to be quite easy to use (because you have too little knowledge, brother). But! BUT! However! Strictly speaking, this approach is not desirable. Why? Quote Google official translation:

Because VM is usually a multi threading execution environment. When each thread calls the native function, the JNIEnv index value passed in is different. In order to cooperate with this multi thread environment, when writing native functions, C component developers can avoid the data conflict of threads by using the different JNIEnv index values, so as to ensure that the written native functions can be safely executed in Android multi thread VM. For this reason, when calling the function of C component, the JNIEnv index value will be passed to the next level function for use.

It seems very abstract and ambiguous. But we must know that VM is multi threading, and every JNIEnv is different! Especially in different threads, they maintain their own independent JNIEnv. So the question goes back to the original? How to correctly obtain thread safe JNIEnv?

At this point, we introduce the function JNI_OnLoad and structural JavaVM, in the header file jni H has its definition:

/*
 * Prototypes for functions exported by loadable shared libs.  These are
 * called by JNI, not provided by JNI.
 */
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);
copy

According to the notes, JNI_OnLoad is called back by the system JNI, which is not used indiscriminately by developers, nor provided by JNI by default. The system will configure by default without rewriting this method. When the virtual machine VM loads c components (so), it will call the component loading interface JNI_OnLoad(), in JNI_ In the onload() function, the JNIEnv index value is obtained through the VM index and stored in the env index variable.

JavaVM here is the representation of virtual machine VM in JNI. There is only one JavaVM object in a process JVM, which is shared by threads. In other words, this JavaVM can be used globally safely, and only in JNI_ The callback of onload performs strong reference assignment. With this JavaVM, we can call AttachCurrentThread to attach the current thread to the virtual machine VM and return the JNIEnv corresponding to the thread, and we will be happy!

Speaking of AttachCurrentThread, we can't help mentioning another interface of JavaVM GetEnv. It seems that GetEnv is the way to get env? Let's explain this. Only after AttachCurrentThread is first assigned to JavaVM and an independent JNIEnv is assigned, the env returned by the secondary pointer of the second parameter of GetEnv can have a value. That is, javavm->GetEnv gets the valid env of this thread. Javavm->AttachCurrentThread is a thread independent env assigned to the virtual machine. Therefore, generally, the first sentence of the thread execution function is AttachCurrentThread, and then GetEnv can be used.

So far as the theoretical knowledge is introduced, let's continue to test the function function. Now the code should be as follows:

JavaVM *javaVM;

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGW("%s\n", "JNI_OnLoad startup ...");
    javaVM = vm;
    JNIEnv *env = NULL;
    jint result;

    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) == JNI_OK) {
        LOGI("Catch JNI_VERSION_1_6\n");
        result = JNI_VERSION_1_6;
    }
    else if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) == JNI_OK) {
        LOGI("Catch JNI_VERSION_1_4\n");
        result = JNI_VERSION_1_4;
    }
    else {
        LOGI("Default JNI_VERSION_1_2\n");
        result = JNI_VERSION_1_2;
    }

    assert(env != NULL);
    // Dynamically register native functions
    return result;
}

void *pthread_run(void *arg) {
    JNIEnv *env = NULL;
    // (*javaVM)->AttachCurrentThread(javaVM,&env,NULL)
    // (*javaVM)->GetEnv(javaVM, (void **)&env, JNI_VERSION_1_6)
    if ( (*javaVM)->AttachCurrentThread(javaVM,&env,NULL) != JNI_OK) {
        LOGE("javaVM->Env Error!\n");
        pthread_exit((void *) -1);
    }

    assert(env != NULL);
   
    // Custom type jclass
    jclass clazz = (*env)->FindClass(env, "org/zzrblog/MainActivity");
    jmethodID getUuid_mid = (*env)->GetStaticMethodID(env, clazz, "getUuid","()Ljava/lang/String;");

    char *name = (char *) arg;
    for (int i = 0; i < 5; ++i) {
        jobject uuid_jstr = (*env)->CallStaticObjectMethod(env, clazz, getUuid_mid);
        char* uuid_cstr = (char *) (*env)->GetStringUTFChars(env, uuid_jstr, NULL);
        LOGI("%s, No:%d, uuid:%s", name, i, uuid_cstr);
        sleep(1);
    }

    (*env)->ReleaseStringUTFChars(env, sys_uuid_jstr, sys_uuid_cstr);
    (*javaVM)->DetachCurrentThread(javaVM);
    pthread_exit((void *) 0);
}
copy

We are in JNI_ The onload function refers to the JavaVM object globally, and then the template code, telling the system which version of JNI is used by the virtual machine. At this time, the env obtained by calling javavm->getenv is the env of the main thread. So we can succeed.

Then we enter the thread execution function and use AttachCurrentThread to request the allocation of the current thread safe env. Then we use JNI API s such as FindClass / GetStaticMethodID / CallStaticObjectMethod to perform mainactivity of the Java layer Getuuid static method call.

Everything looks so smooth, and then run the demo to instantly report an error (smirk.jpg) a pile of red mistakes!

Why can't I find org.zzrblog MainActivity? This problem better reflects the thread independence of JNIEnv! If FindClass uses the main thread Env, there will be no error. If the FindClass is the UUID class of the system, it will not report an error. But there are not so many ifs in real life! The reason for the problem is that only the env of the main thread contains our custom (self-developed) class types, while the thread safe env of AttachCurrentThread only loads the class types of the system, and does not contain custom class types.

So the cause of the problem is found, how to solve it? Since env is not thread safe, it cannot be referenced directly. Then we can reference the calling object shared by other threads, and then get jclass through GetObjectClass. Students who don't understand see the following code:

jobject jMainActivity;

JNIEXPORT void JNICALL
Java_org_zzrblog_MainActivity_nativeThreadEnvTest(JNIEnv *env, jobject thiz) {
    if(jMainActivity == NULL) {
        //Call the object to create a global reference
        jMainActivity = (*env)->NewGlobalRef(env, thiz);
    }

    pthread_t tid;
    pthread_create(&tid, NULL, pthread_run, (void *) "pthread1");
    //void* reval;
    //pthread_join(tid, &reval);
}
copy

Now when you know the static and non static of the native method, the meaning and real use of the second parameter passed in?

When it is not static, pass in thiz of jobobject type, which is equivalent to MainActivity.this.

During staic, clazz of jcalss type is passed in, which is equivalent to MainActivity.class.

After the main content of POSIX thread is written, the next chapter officially carries out the audio and video synchronization transformation of ffmpeg.

Publisher: full stack programmer, stack length, please indicate the source for Reprint: https://javaforall.cn/129225.html Original link: https://javaforall.cn

Posted by exally on Tue, 09 Aug 2022 18:03:01 +0930