美文网首页
JNI教程-语法篇

JNI教程-语法篇

作者: Lenny_liu | 来源:发表于2017-12-21 16:59 被阅读26次

    一.概述

    Hello Everyone,上一篇是教大家配置了开发环境,好多同学对里面的某些代码可能还存在疑惑的,这篇文章就带领代驾熟悉下JNI编程的一些语法知识。
    在开篇之前我们看下,我们以上篇的例子来展开


    image.png
    image.png

    通过javah生成的c++文件,可以看到一个很奇怪的方法。这个就是我们今天的入门了。其中System.loadLibrary(“[库名]”),名字命名是有相应的规则的,而这个库名和CMakeList.txt文件中的库名必须一致。Java虚拟机会找到这个类名并调用该函数。

    静态注册Native方法:

    仔细看这个函数会发现方法名的组成为 Java_包名类名方法名。其中JNIEXPORT和JNICALL是两个宏定义,主要作用就是说明该函数为JNI函数,jstring是定义JNI定义的数据类型。在Java虚拟机加载的时候回连接对应的Native方法,比如在例子中的 public native String getId(); 通过javah操作后会生成

    JNIEXPORT jstring JNICALL Java_com_example_mylibrary_JNITest_getID();
    

    动态注册Native方法:

    JNI_ONLOAD函数:该函数会有两个参数,其中*jvm为Java虚拟机实例,JavaVM结构体定义了以下函数:

    DestroyJavaVM
    AttachCurrentThread
    DetachCurrentThread
    GetEnv

    这里我们使用了GetEnv函数获取JNIEnv变量,上面的JNI_ONLOAD函数有如下代码:

    JNIEnv *env;
    if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    
        return -1;
    }
    

    这里调用了GetEnv函数获取JNIEnv结构体指针,其实JNIEnv结构体是指向一个函数表的,该函数表指向了一个对应的JNI函数,我们通过调用这些JNI函数实现JNI编程。

    获取JAVA对象,完成动态注册

    上面介绍 如何获取JNIEnv结构体指针,得到结构体指针我们就可以调用JNIEnv中的RegisterNatives函数完成动态注册native方法:

    jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
    
    

    第一个参数是Java层对应包含native方法的对象,指的就是JNITest对象了,通过调用JNIEnv对应的函数获取class对象

    jclass clz = env->FindClass("com/github/songnick/jni/AndroidJni");
    
    

    第二个参数是JNINativeMethod结构体指针,这里 JNINative结构体是描述Java层Native方法的,定义如下:

    typedef struct {
        const char* name;//Java层native方法的名字
        const char* signature;//Java层native方法的描述符
        void*       fnPtr;//对应JNI函数的指针
    } JNINativeMethod;
    

    第三个参数为注册native方法的数量,一般会动态注册多个native方法,首先会定义一个JNINativeMethod数组,然后将该指针作为RegisterNative函数的参数传入

    JNINativeMethod nativeMethod[] = {{"dynamicLog", "()V", (void*)nativeDynamicLog}};
    

    最后调用RegisterNative函数完成动态注册:

    env->RegisterNatives(clz, nativeMethod, sizeof(nativeMethod)/sizeof(nativeMethod[0]));
    

    JNIEnv结构体

    JNIEnv结构体指向一个函数表,该函数表指向一系列的JNI函数,我们通过调用这些JNI函数可以与Java层进行交互,以下是常用的函数:

    ..........
    jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    jboolean GetBooleanField(jobject obj, jfieldID fieldID)
    jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    CallVoidMethod(jobject obj, jmethodID methodID, ...)
    CallBooleanMethod(jobject obj, jmethodID methodID, ...)
    ..........

    • GetFieldID函数是获取Java对象中某个变量的ID
    • GetBooleanField()函数是根据变量的ID获取数据类型为Boolean
    • GetMethodID函数获取Java对象中对应的方法ID
    • CallVoidMethod根据methodID调用对应对象中的方法,并且该方法的返回值Void类型
    • CallBooleanMethod根据methodID调用对应对象中的方法,并且该方法的返回值Boolean类型

    JNI数据类型

    JNI的本质是衔接Java和C++/C 相互通信的,因为Java的对象是不被C++识别的,同样的C++的指针也不被Java识别,所以JNI需要自定义一些自己的数据类型来使双方的通信保持顺畅。

    1.原始数据类型Java Type Native Typ Description
    JavaType Native Description
    boolean jboolean unsigned 8 bits
    byte jbyte signed 8 bits
    char jchar unsigned 16 bits
    short jshort signed 16 bits
    int jint signed 32 bits
    long jlong signed 64 bits
    float jfloat 32 bits
    double jdouble 64 bits
    void void N/A
    2.引用类型

    前面我们在获取AndroidJni对象的使用通过定义jclass引用,然后调用FindClass函数获取了该对象,所以JNI也定义了一些引用类型以便JNI层调用,具体的引用类型如下:

    jobject (all Java objects)
    |
    |-- jclass (java.lang.Class objects)
    |-- jstring (java.lang.String objects)
    |-- jarray (array)
    | |--jobjectArray (object arrays)
    | |--jbooleanArray (boolean arrays)
    | |--jbyteArray (byte arrays)
    | |--jcharArray (char arrays)
    | |--jshortArray (short arrays)
    | |--jintArray (int arrays)
    | |--jlongArray (long arrays)
    | |--jfloatArray (float arrays)
    | |--jdoubleArray (double arrays)
    |
    |--jthrowable

    3.方法和变量的ID
     当需要调用Java中的某个方法的时候我们首先要获取它的ID,根据ID调用JNI函数获取该方法,变量的获取过程也是同样的过程,这些ID的结构体定义如下:

    struct _jfieldID;              /* opaque structure */ 
    typedef struct _jfieldID *jfieldID;   /* field IDs */ 
    
    struct _jmethodID;              /* opaque structure */ 
    typedef struct _jmethodID *jmethodID; /* method IDs */
    
    描述符

    1.类描述符
    前面为了获取Java的JNITest对象,是通过调用FindClass()函数获取的,该函数参数只有一个字符串参数,字符串如下:
    >com/example/mylibrary/JNITest

    这个就是我们JNI定义了对类的描述符,规则也很简单,包名+类名,并用“/”替换“.”
    2.方法描述符
    前面我们动态注册native方法的时候结构体JNINativeMethod中含有方法描述符,就是确定native方法的参数和返回值,

    Method Descriptor Java Language Type
    "()Ljava/lang/String;" String f();
    "(ILjava/lang/Class;)J" long f(int i, Class c);
    "([B)V" String f(byte[] bytes);

    上面的栗子我们看到方法的返回类型和方法参数有引用类型以及boolean、int等基本数据类型,对于这些类型的描述符在下个部分介绍。这里数组的描述符以"["和对应的类型描述符来表述。对于二维数组以及三维数组则以"[["和"[[["表示:

    Descriptor Java Langauage Type
    "[[I" int[][]
    "[[[D" double[][][]

    3.数据类型描述符
     前面我们说了方法的描述符,那么针对boolean、int等数据类型描述符是怎样的呢,JNI对基本数据类型的描述符定义如下:

    Descriptor Java Langauage Type
    Z boolean
    B byte
    C char
    S short
    I int
    J long
    F float
    D double

    Field Desciptor Java Language Type
    Z boolean
    B byte
    C char
    S short
    I int
    J long
    F floa
    D double

    Descriptor Java Langauage Type
    Z boolean
    B byte
    C char
    S short
    I int
    J long
    F float
    D double

    对于引用类型描述符是以"L"开头";"结尾,示例如下所示:

    Field Desciptor Java Language Type
    "Ljava/lang/String;" String
    "[Ljava/lang/Object;"

    今天的学习就到这里了,该篇文章只是介绍了下基本的语法知识以及jni通信的基本方法。后续我们接着进行~

    相关文章

      网友评论

          本文标题:JNI教程-语法篇

          本文链接:https://www.haomeiwen.com/subject/wawowxtx.html