美文网首页
JNI一文详解

JNI一文详解

作者: 长夜西风 | 来源:发表于2020-08-19 01:13 被阅读0次

    1. 准备工作

    • Ubuntu更换阿里云软件源

    • 在Ubuntu上安装gcc和g++

    • 在Ubuntu上安装OpenJDK

      sudo apt install default—jdk
      

      设置JAVA_HOME环境变量:找到OpenJDK的安装路径(e.g. /usr/lib/jvm/java—11—openjdk—amd64)

      sudo vim /etc/profile
      

      在文件末尾添加

      export JAVA_HOME=/usr/lib/jvm/java—11—openjdk—amd64
      export PATH=$PATH:$JAVA_HOME/bin
      

      检查是否配置成功

      echo $JAVA_HOME
      echo $PATH
      

    2. JNI简介

    全称Java Native Interface,主要用于在Java代码中调用非Java代码,以此绕过Java的内存管理和解决Java的性能问题。

    3. Getting Started

    3.1 JNI with C

    先看一下Java调用C的简单示例:

    public class HelloJNI {  // Save as HelloJNI.java
       static {
          System.loadLibrary("hello"); // Load native library hello.dll (Windows) or libhello.so (Unixes)
                                       //  at runtime
                                       // This library contains a native method called sayHello()
       }
     
       // Declare an instance native method sayHello() which receives no parameter and returns void
       private native void sayHello();
     
       // Test Driver
       public static void main(String[] args) {
          new HelloJNI().sayHello();  // Create an instance and invoke the native method
       }
    }
    

    关键在于System.loadLibrary("hello");这行代码,Java通过这行代码调用动态链接库(Windows中的后缀名是.dll,Unix系OS中是.so),在运行时Java文件时可以通过设置虚拟机参数—Djava.library.path=/path/to/lib将本地库引入。

    通过下面的代码来编译Java代码并生成对应的头文件:

    javac —h . HelloJNI.java
    

    生成的头文件代码如下:

    /* DO NOT EDIT THIS FILE — it is machine generated */
    #include <jni.h>
    /* Header—class HelloJNI */
     
    #ifndef _Included_HelloJNI
    #define _Included_HelloJNI
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     HelloJNI
     * Method:    sayHello
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
     
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    头文件中定义了一个函数:

    JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
    

    依照惯例函数的命名规则是Java_{package_and_classname}_{function_name}(JNI_arguments)。

    函数的两个参数的含义分别是:

    • JNIEnv*: reference to JNI environment, which lets you access all the JNI functions.
    • jobject: reference to "this" Java object.

    extern "C"只被C++编译器识别,用于提示编译器这些函数应当使用C的函数命名协议来编译,C和C++的函数命名协议不同,C++支持重载。

    下面的代码是要被调用的C文件:

    // Save as "HelloJNI.c"
    #include <jni.h>        // JNI header provided by JDK
    #include <stdio.h>      // C Standard IO Header
    #include "HelloJNI.h"   // Generated
     
    // Implementation of the native method sayHello()
    JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
       printf("Hello World!\n");
       return;
    }
    

    jni.h是包含在JDK中的,不同OS平台的存放位置不同。

    下面来编译和运行:

    $ gcc —fPIC —I"$JAVA_HOME/include" —I"$JAVA_HOME/include/linux" —shared —o libhello.so HelloJNI.c
    
    $ java —Djava.library.path=. HelloJNI
    

    3.2 JNI with C++

    将上一个模块中c文件替换成cpp文件:

    // Save as "HelloJNI.cpp"
    #include <jni.h>       // JNI header provided by JDK
    #include <iostream>    // C++ standard IO header
    #include "HelloJNI.h"  // Generated
    using namespace std;
    
    // Implementation of the native method sayHello()
    JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
        cout << "Hello World from C++!" << endl;
       return;
    }
    

    编译:

    $ g++ —fPIC —I"$JAVA_HOME/include" —I"$JAVA_HOME/include/linux" —shared —o libhello.so HelloJNI.cpp
    

    运行:

    java —Djava.library.path=. HelloJNI
    

    4. JNI 基础

    JNI中定义了一下两类数据类型和Java中的类型来对应:

    • 基础类型:

      jint—int
      jbyte—byte
      jshort—short
      jlong—long
      jfloat—float
      jdouble—double
      jchar—char
      jboolean—boolean

    • 引用类型:

      jobject—java.lang.Object

      jclass—java.lang.Class

      jstringjava.lang.String

      jthrowablejava.lang.Throwable

      jarray—Java array jarray包括八种基础类型(jintArray,jbyteArray,jshortArray,jlongArray,jfloatArray,jdoubleArray,jcharArrayandjbooleanArray)和一种引用类型(jobjectArray)

    native程序要做的事是:

    1. 通过上面的JNI类型获取函数参数,这些参数都是从Java层传递过来的。
    2. 同样,JNI类型和native类型也有对应关系,例如:jstring对应c的string类型,jintArray对应c的int[]类型,jni中的基础类型像jint这种不需要转换,可以直接操作。
    3. 执行操作。
    4. 创建一个jni类型的返回对象,然后把返回值复制到这个对象中。
    5. 返回。

    最困难的一步就是jni类型和native类型的转换,jni为此提供了很多的转换接口。

    jni本质上是一个c的接口,它并不会真正传递对象。

    5. 在Java和native之间传递参数和返回结果

    5.1 传递基础类型

    jni和java基础类型不需要转换,八种jni基础类型和java基础类型一一对应。

    jni和c基础类型需要转换,在"jni.h" 和"win32\jni_mh.h"中可以查看类型定义的声明:

    // In "win\jni_mh.h" - machine header which is machine dependent
    typedef long            jint;
    typedef __int64         jlong;
    typedef signed char     jbyte;
     
    // In "jni.h"
    typedef unsigned char   jboolean;
    typedef unsigned short  jchar;
    typedef short           jshort;
    typedef float           jfloat;
    typedef double          jdouble;
    typedef jint            jsize;
    

    需要注意的是:jint对应着c中的long(至少32位),而不是int(可以为16位)。

    TestJNIPrimitive.java:
    public class TestJNIPrimitive {
       static {
          System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
       }
     
       // Declare a native method average() that receives two ints and return a double containing the average
       private native double average(int n1, int n2);
     
       // Test Driver
       public static void main(String args[]) {
          System.out.println("In Java, the average is " + new TestJNIPrimitive().average(3, 2));
       }
    }
    
    TestJNIPrimitive.c:
    #include <jni.h>
    #include <stdio.h>
    #include "TestJNIPrimitive.h"
     
    JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
              (JNIEnv *env, jobject thisObj, jint n1, jint n2) {
       jdouble result;
       printf("In C, the numbers are %d and %d\n", n1, n2);
       result = ((jdouble)n1 + n2) / 2.0;
       // jint is mapped to int, jdouble is mapped to double
       return result;
    }
    
    TestJNIPrimitive.cpp:
    #include <jni.h>
    #include <iostream>
    #include "TestJNIPrimitive.h"
    using namespace std;
     
    JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
              (JNIEnv *env, jobject obj, jint n1, jint n2) {
       jdouble result;
       cout << "In C++, the numbers are " << n1 << " and " << n2 << endl;
       result = ((jdouble)n1 + n2) / 2.0;
       // jint is mapped to int, jdouble is mapped to double
       return result;
    }
    

    5.2 传递String类型

    头文件中的函数定义:

    JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *, jobject, jstring);
    

    在jni和c之间传递string类型比基本类型更加复杂,需要在jni类型jstring和c类型char*之间做转换。

    针对这种转换,jni提供了两个函数:

    • jstring—>char*

      const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)
      
    • char*—>jstring

      jstring NewStringUTF(JNIEnv*, char*)
      

    示例代码:

    TestJNIString.c
    #include <jni.h>
    #include <stdio.h>
    #include "TestJNIString.h"
     
    JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
       // Step 1: Convert the JNI String (jstring) into C-String (char*)
       const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL);
       if (NULL == inCStr) return NULL;
     
       // Step 2: Perform its intended operations
       printf("In C, the received string is: %s\n", inCStr);
       (*env)->ReleaseStringUTFChars(env, inJNIStr, inCStr);  // release resources
     
       // Prompt user for a C-string
       char outCStr[128];
       printf("Enter a String: ");
       scanf("%s", outCStr);    // not more than 127 characters
     
       // Step 3: Convert the C-string (char*) into JNI String (jstring) and return
       return (*env)->NewStringUTF(env, outCStr);
    }
    

    jni支持Unicode (16-bit characters) 和 UTF-8 (encoded in 1-3 bytes) 两种类型的string,UTF-8类似于c中的char array(从本质而言,C语言把字符串字面量作为字符数组来处理。当C语言编译器在程序中遇到长度为n的字符串字面量时,它会为字符串字面量分配长度为n+1的内存空间。这块内存空间将用来存储字符串字面量中的字符,以及一个用来标志字符串末尾的额外字符(空字符)。空字符是一个所有位都为0的字节,因此用转义序列\0来表示,空字符的码值为0),用于C/C++。

    // UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)
    // Can be mapped to null-terminated char-array C-string
    const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
       // Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.
    void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
       // Informs the VM that the native code no longer needs access to utf.
    jstring NewStringUTF(JNIEnv *env, const char *bytes);
       // Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.
    jsize GetStringUTFLength(JNIEnv *env, jstring string);
       // Returns the length in bytes of the modified UTF-8 representation of a string.
    void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
       // Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding 
       // and place the result in the given buffer buf.
      
    // Unicode Strings (16-bit character)
    const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
       // Returns a pointer to the array of Unicode characters
    void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
       // Informs the VM that the native code no longer needs access to chars.
    jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);
       // Constructs a new java.lang.String object from an array of Unicode characters.
    jsize GetStringLength(JNIEnv *env, jstring string);
       // Returns the length (the count of Unicode characters) of a Java string.
    void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);
       // Copies len number of Unicode characters beginning at offset start to the given buffer buf
    

    函数GetStringUTFChars()用于从一个java的jstring类型生成c的char*类型:

    // Returns a pointer to an array of bytes representing the string in modified UTF-8             encoding.
    const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
    

    其中的第三个参数(类型为jboolean*), isCopy的值为JNI_TRUE的时候说明函数的返回值是原始的String实例的复制,为JNI_FALSE的时候则是指向原始实例的指针(这种情况下native层不可以修改字符串的内容)。一般来说jni优先返回一个指针,其次是返回一个复制实例。但是,我们很少修改基础字符串,所以第三个参数经常传递NULL指针。

    ReleaseStringUTFChars()和GetStringUTFChars()必须成对出现,用来释放内存和引用,使对象可以被GC回收。

    函数NewStringUTF()通过c的string创建一个新的jstring。

    JDK1.2中引入了GetStringUTFRegion()可以用来替代GetStringUTFChars(),isCopy不再需要,因为这个函数直接将jstring复制到了c的char array中。

    JDK 1.2中还引入了函数Get/ReleaseStringCritical(),这两个函数成对出现,且中间不能被阻塞。它的功能和GetStringUTFChars()类似,一般返回指针,如果失败的话,会返回一个复制实例。

    示例代码:

    TestJNIString.cpp
    #include <jni.h>
    #include <iostream>
    #include <string>
    #include "TestJNIString.h"
    using namespace std;
     
    JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
       // Step 1: Convert the JNI String (jstring) into C-String (char*)
       const char *inCStr = env->GetStringUTFChars(inJNIStr, NULL);
       if (NULL == inCStr) return NULL;
     
       // Step 2: Perform its intended operations
       cout << "In C++, the received string is: " << inCStr << endl;
       env->ReleaseStringUTFChars(inJNIStr, inCStr);  // release resources
     
       // Prompt user for a C++ string
       string outCppStr;
       cout << "Enter a String: ";
       cin >> outCppStr;
     
       // Step 3: Convert the C++ string to C-string, then to JNI String (jstring) and return
       return env->NewStringUTF(outCppStr.c_str());
    }
    

    注意c++和c的string函数语法不通,在c++中,我们可以用 "env->"代替"(env*)->",此外,在c++函数中我们可以省略JNIEnv* 参数。c++支持string类,也支持c中的char array。

    5.3 传递基础类型数组

    TestJNIPrimitiveArray.java
    public class TestJNIPrimitiveArray {
       static {
          System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
       }
     
       // Declare a native method sumAndAverage() that receives an int[] and
       //  return a double[2] array with [0] as sum and [1] as average
       private native double[] sumAndAverage(int[] numbers);
     
       // Test Driver
       public static void main(String args[]) {
          int[] numbers = {22, 33, 33};
          double[] results = new TestJNIPrimitiveArray().sumAndAverage(numbers);
          System.out.println("In Java, the sum is " + results[0]);
          System.out.println("In Java, the average is " + results[1]);
       }
    }
    

    生成的头文件中的函数定义如下:

    JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage (JNIEnv *, jobject, jintArray);
    

    在java中,数组是引用类型。有九种数组类型,包括8个基础类型和一个引用类型。jni分别对应地定义了8种基础类型数组,jintArray,jbyteArray,jshortArray,jlongArray,jfloatArray,jdoubleArray,jcharArray,jbooleanArray,对应java中的8种基础类型:int,byte,short,long,float,double,char,boolean。jobjectArray对应引用类型数组。

    和前面介绍的基础类型类似,需要在java类型和jni类型之间进行转换:

    jni的jintArray—>c的jint[]

    jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)
    

    c的jint[]—>jni的jintArray

    Step1 : 分配内存

    jintArray NewIntArray(JNIEnv *env, jsize len)
    

    Step2 : 从 jint[] 复制内容到 jintArray

    void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)
    

    上述的函数有八种,对应8个基础类型。

    native代码需要做的事:

    1. 把jni数组转换成c的数组
    2. 执行操作
    3. 把c的数组转换成jni的数组,然后返回
    TestJNIPrimitiveArray.c
    #include <jni.h>
    #include <stdio.h>
    #include "TestJNIPrimitiveArray.h"
     
    JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage
              (JNIEnv *env, jobject thisObj, jintArray inJNIArray) {
       // Step 1: Convert the incoming JNI jintarray to C's jint[]
       jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
       if (NULL == inCArray) return NULL;
       jsize length = (*env)->GetArrayLength(env, inJNIArray);
     
       // Step 2: Perform its intended operations
       jint sum = 0;
       int i;
       for (i = 0; i < length; i++) {
          sum += inCArray[i];
       }
       jdouble average = (jdouble)sum / length;
       (*env)->ReleaseIntArrayElements(env, inJNIArray, inCArray, 0); // release resources
     
       jdouble outCArray[] = {sum, average};
     
       // Step 3: Convert the C's Native jdouble[] to JNI jdoublearray, and return
       jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2);  // allocate
       if (NULL == outJNIArray) return NULL;
       (*env)->SetDoubleArrayRegion(env, outJNIArray, 0 , 2, outCArray);  // copy
       return outJNIArray;
    }
    
    jni基础类型数组函数
    // ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
    // PrimitiveType: int, byte, short, long, float, double, char, boolean
    // NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean
    NativeType * Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
    void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
    void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, NativeType *buffer);
    void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, const NativeType *buffer);
    ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
    void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
    void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);
    

    GET|Release<*PrimitiveType*>ArrayElements() 用于从java的jxxxArray创建c的array jxxx[]

    GET|Set<*PrimitiveType*>ArrayRegion() 用于将一个jxxxArray复制到预分配的c的 jxxx[],或者从c的 jxxx[]复制到jxxxArray中。

    The New<PrimitiveType>Array() 用于给 jxxxArray 分配一个给定的大小,然后可以用Set<*PrimitiveType*>ArrayRegion() 从一个jxxx[]中获取内容填充 jxxxArray

    Get|ReleasePrimitiveArrayCritical()成对出现,中间不允许被阻塞。

    6. 获取java类的成员变量/方法和类静态变量/方法

    6.1 获取类的成员变量

    1. 通过 GetObjectClass()获取对象的类的引用。

    2. 获取成员ID GetFieldID()。需要提供变量名和成员描述。格式如下:"L<fully-qualified-name>;"。用/代替.。例如:String - "Ljava/lang/String;"。

      对于基础类型:

      "I" for int,

      "B" for byte,

      "S" for short,

      "J" for long,

      "F" for float,

      "D" for double,

      "C" for char,

      "Z" for boolean

      对于数组:

      要加一个前缀"[",例如 Object array-"[Ljava/lang/Object;",int array-"[I"

    3. 基于 Field ID, 通过 GetObjectField() or Get<primitive-type>Field() 获取成员变量。

    4. 使用 SetObjectField() or Set<primitive-type>Field() 修改成员变量。

    示例代码:

    TestJNIInstanceVariable.java
    public class TestJNIInstanceVariable {
       static {
          System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
       }
     
       // Instance variables
       private int number = 88;
       private String message = "Hello from Java";
     
       // Declare a native method that modifies the instance variables
       private native void modifyInstanceVariable();
     
       // Test Driver   
       public static void main(String args[]) {
          TestJNIInstanceVariable test = new TestJNIInstanceVariable();
          test.modifyInstanceVariable();
          System.out.println("In Java, int is " + test.number);
          System.out.println("In Java, String is " + test.message);
       }
    }
    
    TestJNIInstanceVariable.c
    #include <jni.h>
    #include <stdio.h>
    #include "TestJNIInstanceVariable.h"
     
    JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable
              (JNIEnv *env, jobject thisObj) {
       // Get a reference to this object's class
       jclass thisClass = (*env)->GetObjectClass(env, thisObj);
     
       // int
       // Get the Field ID of the instance variables "number"
       jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
       if (NULL == fidNumber) return;
     
       // Get the int given the Field ID
       jint number = (*env)->GetIntField(env, thisObj, fidNumber);
       printf("In C, the int is %d\n", number);
     
       // Change the variable
       number = 99;
       (*env)->SetIntField(env, thisObj, fidNumber, number);
     
       // Get the Field ID of the instance variables "message"
       jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
       if (NULL == fidMessage) return;
     
       // String
       // Get the object given the Field ID
       jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);
     
       // Create a C-string with the JNI String
       const char *cStr = (*env)->GetStringUTFChars(env, message, NULL);
       if (NULL == cStr) return;
     
       printf("In C, the string is %s\n", cStr);
       (*env)->ReleaseStringUTFChars(env, message, cStr);
     
       // Create a new C-string and assign to the JNI string
       message = (*env)->NewStringUTF(env, "Hello from C");
       if (NULL == message) return;
     
       // modify the instance variables
       (*env)->SetObjectField(env, thisObj, fidMessage, message);
    }
    

    获取成员变量的jni方法:

    jclass GetObjectClass(JNIEnv *env, jobject obj);
       // Returns the class of an object.
       
    jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
      // Returns the field ID for an instance variable of a class.
     
    NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
    void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
      // Get/Set the value of an instance variable of an object
      // <type> includes each of the eight primitive types plus Object.
    

    6.2 获取类静态变量

    获取静态变量和实例变量类似,只是在调用的函数上有所差别,GetStaticFieldID(),Get|SetStaticObjectField(),Get|SetStatic<Primitive-type>Field()。

    TestJNIStaticVariable.java
    public class TestJNIStaticVariable {
       static {
          System.loadLibrary("myjni"); // nyjni.dll (Windows) or libmyjni.so (Unixes)
       }
     
       // Static variables
       private static double number = 55.66;
     
       // Declare a native method that modifies the static variable
       private native void modifyStaticVariable();
     
       // Test Driver
       public static void main(String args[]) {
          TestJNIStaticVariable test = new TestJNIStaticVariable();
          test.modifyStaticVariable();
          System.out.println("In Java, the double is " + number);
       }
    }
    
    TestJNIStaticVariable.c
    #include <jni.h>
    #include <stdio.h>
    #include "TestJNIStaticVariable.h"
     
    JNIEXPORT void JNICALL Java_TestJNIStaticVariable_modifyStaticVariable
              (JNIEnv *env, jobject thisObj) {
       // Get a reference to this object's class
       jclass cls = (*env)->GetObjectClass(env, thisObj);
     
       // Read the int static variable and modify its value
       jfieldID fidNumber = (*env)->GetStaticFieldID(env, cls, "number", "D");
       if (NULL == fidNumber) return;
       jdouble number = (*env)->GetStaticDoubleField(env, cls, fidNumber);
       printf("In C, the double is %f\n", number);
       number = 77.88;
       (*env)->SetStaticDoubleField(env, cls, fidNumber, number);
    }
    

    获取静态变量的jni函数:

    jfieldID GetStaticFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
      // Returns the field ID for a static variable of a class.
     
    NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);
    void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
      // Get/Set the value of a static variable of a class.
      // <type> includes each of the eight primitive types plus Object.
    

    6.3. 调用Java类的成员方法和类静态方法

    调用成员方法的步骤:

    step1:通过 GetObjectClass()获取对象的类的引用。

    step2:GetMethodID()获取Method ID,提供方法名和签名,签名的格式是 "(parameters)return-type"。可通过javap utility (Class File Disassembler) with -s (print signature) and -p (show private members)这个命令来列出java类的方法签名:

    > javap --help
    > javap -s -p TestJNICallBackMethod
      .......
      private void callback();
        Signature: ()V
     
      private void callback(java.lang.String);
        Signature: (Ljava/lang/String;)V
     
      private double callbackAverage(int, int);
        Signature: (II)D
     
      private static java.lang.String callbackStatic();
        Signature: ()Ljava/lang/String;
      .......
    

    step3:基于Method ID,调用Call<Primitive-type>Method()orCallVoidMethod()orCallObjectMethod(),返回值类型分别是<Primitive-type>, void and Object,对于有参数的方法要提供参数。

    示例代码:

    TestJNICallBackMethod.java
    public class TestJNICallBackMethod {
       static {
          System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
       }
     
       // Declare a native method that calls back the Java methods below
       private native void nativeMethod();
     
       // To be called back by the native code
       private void callback() {
          System.out.println("In Java");
       }
     
       private void callback(String message) {
          System.out.println("In Java with " + message);
       }
     
       private double callbackAverage(int n1, int n2) {
          return ((double)n1 + n2) / 2.0;
       }
     
       // Static method to be called back
       private static String callbackStatic() {
          return "From static Java method";
       }
    
       // Test Driver 
       public static void main(String args[]) {
          new TestJNICallBackMethod().nativeMethod();
       }
    }
    
    TestJNICallBackMethod.c
    #include <jni.h>
    #include <stdio.h>
    #include "TestJNICallBackMethod.h"
     
    JNIEXPORT void JNICALL Java_TestJNICallBackMethod_nativeMethod
              (JNIEnv *env, jobject thisObj) {
     
       // Get a class reference for this object
       jclass thisClass = (*env)->GetObjectClass(env, thisObj);
     
       // Get the Method ID for method "callback", which takes no arg and return void
       jmethodID midCallBack = (*env)->GetMethodID(env, thisClass, "callback", "()V");
       if (NULL == midCallBack) return;
       printf("In C, call back Java's callback()\n");
       // Call back the method (which returns void), baed on the Method ID
       (*env)->CallVoidMethod(env, thisObj, midCallBack);
     
       jmethodID midCallBackStr = (*env)->GetMethodID(env, thisClass,
                                   "callback", "(Ljava/lang/String;)V");
       if (NULL == midCallBackStr) return;
       printf("In C, call back Java's called(String)\n");
       jstring message = (*env)->NewStringUTF(env, "Hello from C");
       (*env)->CallVoidMethod(env, thisObj, midCallBackStr, message);
     
       jmethodID midCallBackAverage = (*env)->GetMethodID(env, thisClass,
                                      "callbackAverage", "(II)D");
       if (NULL == midCallBackAverage) return;
               jdouble average = (*env)->CallDoubleMethod(env, thisObj, midCallBackAverage, 2, 3);
       printf("In C, the average is %f\n", average);
     
       jmethodID midCallBackStatic = (*env)->GetStaticMethodID(env, thisClass,
                                     "callbackStatic", "()Ljava/lang/String;");
       if (NULL == midCallBackStatic) return;
       jstring resultJNIStr = (*env)->CallStaticObjectMethod(env, thisClass, midCallBackStatic);
       const char *resultCStr = (*env)->GetStringUTFChars(env, resultJNIStr, NULL);
       if (NULL == resultCStr) return;
       printf("In C, the returned string is %s\n", resultCStr);
       (*env)->ReleaseStringUTFChars(env, resultJNIStr, resultCStr);
    }
    

    如果要调用静态方法,使用GetStaticMethodID(), CallStatic<Primitive-type>Method(),CallStaticVoidMethod() or CallStaticObjectMethod()。

    调用成员和静态方法的jni函数:

    jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
       // Returns the method ID for an instance method of a class or interface.
       
    NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
    NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
    NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
       // Invoke an instance method of the object.
       // The <type> includes each of the eight primitive and Object.
       
    jmethodID GetStaticMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
       // Returns the method ID for an instance method of a class or interface.
       
    NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
    NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
    NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
       // Invoke an instance method of the object.
       // The <type> includes each of the eight primitive and Object.
    

    6.4. 调用父类的实例方法

    CallNonvirtual<Type>Method(),类似于java中的super.methodName()。

    具体步骤:

    step1:GetMethodID()

    step2:CallNonvirtual<Type>Method()

    jni函数如下:

    NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, ...);
    NativeType CallNonvirtual<type>MethodA(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, const jvalue *args);
    NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, va_list args);
    

    7. 创建java对象和对象数组

    在native中调用NewObject()andnewObjectArray(),构建jobjectandjobjectArray,然后返回到java中。

    7.1 在native中调用java的构造方法创建java对象

    调用构造方法和调用实例方法类似:

    step1: 获取Method ID method name:"<init>" return-type:"V"

    step2: 调用NewObject()创建java对象

    示例代码:

    TestJavaConstructor.java
    public class TestJNIConstructor {
       static {
          System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
       }
     
       // Native method that calls back the constructor and return the constructed object.
       // Return an Integer object with the given int.
       private native Integer getIntegerObject(int number);
     
       public static void main(String args[]) {
          TestJNIConstructor obj = new TestJNIConstructor();
          System.out.println("In Java, the number is :" + obj.getIntegerObject(9999));
       }
    }
    
    TestJavaConstructor.c
    #include <jni.h>
    #include <stdio.h>
    #include "TestJNIConstructor.h"
     
    JNIEXPORT jobject JNICALL Java_TestJNIConstructor_getIntegerObject
              (JNIEnv *env, jobject thisObj, jint number) {
       // Get a class reference for java.lang.Integer
       jclass cls = (*env)->FindClass(env, "java/lang/Integer");
     
       // Get the Method ID of the constructor which takes an int
       jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(I)V");
       if (NULL == midInit) return NULL;
       // Call back constructor to allocate a new instance, with an int argument
       jobject newObj = (*env)->NewObject(env, cls, midInit, number);
     
       // Try running the toString() on this newly create object
       jmethodID midToString = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;");
       if (NULL == midToString) return NULL;
       jstring resultStr = (*env)->CallObjectMethod(env, newObj, midToString);
       const char *resultCStr = (*env)->GetStringUTFChars(env, resultStr, NULL);
       printf("In C: the number is %s\n", resultCStr);
    
       //May need to call releaseStringUTFChars() before return
       return newObj;
    }
    

    用于创建对象的jni函数:

    jclass FindClass(JNIEnv *env, const char *name);
     
    jobject NewObject(JNIEnv *env, jclass cls, jmethodID methodID, ...);
    jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
    jobject NewObjectV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args);
       // Constructs a new Java object. The method ID indicates which constructor method to invoke
     
    jobject AllocObject(JNIEnv *env, jclass cls);
      // Allocates a new Java object without invoking any of the constructors for the object.
    

    7.2 对象数组

    不同于基础类型的数组可以批量处理所有元素,对象数组需要使用Get|SetObjectArrayElement()处理每个元素。

    示例代码:创建一个Integer数组,计算总和和平均值,返回Double数组。

    TestJNIObjectArray.java
    import java.util.ArrayList;
     
    public class TestJNIObjectArray {
       static {
          System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
       }
       // Native method that receives an Integer[] and
       //  returns a Double[2] with [0] as sum and [1] as average
       private native Double[] sumAndAverage(Integer[] numbers);
     
       public static void main(String args[]) {
          Integer[] numbers = {11, 22, 32};  // auto-box
          Double[] results = new TestJNIObjectArray().sumAndAverage(numbers);
          System.out.println("In Java, the sum is " + results[0]);  // auto-unbox
          System.out.println("In Java, the average is " + results[1]);
       }
    }
    
    TestJNIObjectArray.c
    #include <jni.h>
    #include <stdio.h>
    #include "TestJNIObjectArray.h"
     
    JNIEXPORT jobjectArray JNICALL Java_TestJNIObjectArray_sumAndAverage
              (JNIEnv *env, jobject thisObj, jobjectArray inJNIArray) {
       // Get a class reference for java.lang.Integer
       jclass classInteger = (*env)->FindClass(env, "java/lang/Integer");
       // Use Integer.intValue() to retrieve the int
       jmethodID midIntValue = (*env)->GetMethodID(env, classInteger, "intValue", "()I");
       if (NULL == midIntValue) return NULL;
     
       // Get the value of each Integer object in the array
       jsize length = (*env)->GetArrayLength(env, inJNIArray);
       jint sum = 0;
       int i;
       for (i = 0; i < length; i++) {
          jobject objInteger = (*env)->GetObjectArrayElement(env, inJNIArray, i);
          if (NULL == objInteger) return NULL;
          jint value = (*env)->CallIntMethod(env, objInteger, midIntValue);
          sum += value;
       }
       double average = (double)sum / length;
       printf("In C, the sum is %d\n", sum);
       printf("In C, the average is %f\n", average);
     
       // Get a class reference for java.lang.Double
       jclass classDouble = (*env)->FindClass(env, "java/lang/Double");
     
       // Allocate a jobjectArray of 2 java.lang.Double
       jobjectArray outJNIArray = (*env)->NewObjectArray(env, 2, classDouble, NULL);
     
       // Construct 2 Double objects by calling the constructor
       jmethodID midDoubleInit = (*env)->GetMethodID(env, classDouble, "<init>", "(D)V");
       if (NULL == midDoubleInit) return NULL;
       jobject objSum = (*env)->NewObject(env, classDouble, midDoubleInit, (double)sum);
       jobject objAve = (*env)->NewObject(env, classDouble, midDoubleInit, average);
       // Set to the jobjectArray
       (*env)->SetObjectArrayElement(env, outJNIArray, 0, objSum);
       (*env)->SetObjectArrayElement(env, outJNIArray, 1, objAve);
     
       return outJNIArray;
    }
    

    jni中用于创建和操作对象数组的函数:

    jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
       // Constructs a new array holding objects in class elementClass.
       // All elements are initially set to initialElement.
     
    jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
       // Returns an element of an Object array.
     
    void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
       // Sets an element of an Object array.
    

    8. jni中的局部引用和全局引用

    对于追求效率的程序而言,管理引用非常关键,有助于节省开销,提高可重用性。

    jni把对象引用(对于jobject的引用)分为两种:局部和全局引用。

    1. 局部引用在native方法内创建,一旦方法结束就被释放。它的生命周期和方法相同。你也可以调用DeleteLocalRef()主动释放,使对象可以被回收。对象都是作为局部引用被传递给native方法的。所有的jni函数返回的对象都是局部引用,通过CallStaticObjectMethod()获取的jobject也是局部引用
    2. 全局引用会一直存在知道通过DeleteGlobalRef()被主动释放,可以用一个局部引用通过NewGlobalRef()来创建全局引用。

    示例代码:在java中定义两个native方法,都用于创建Integer对象。

    public class TestJNIReference {
       static {
          System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
       }
     
       // A native method that returns a java.lang.Integer with the given int.
       private native Integer getIntegerObject(int number);
     
       // Another native method that also returns a java.lang.Integer with the given int.
       private native Integer anotherGetIntegerObject(int number);
     
       public static void main(String args[]) {
          TestJNIReference test = new TestJNIReference();
          System.out.println(test.getIntegerObject(1));
          System.out.println(test.getIntegerObject(2));
          System.out.println(test.anotherGetIntegerObject(11));
          System.out.println(test.anotherGetIntegerObject(12));
          System.out.println(test.getIntegerObject(3));
          System.out.println(test.anotherGetIntegerObject(13));
       }
    }
    

    在c代码中我们要通过FindClass()获取class java.lang.Integer的引用,然后获取构造方法的method ID。但是我们想复用class ref 和method ID。

    下面看一个错误的使用情况:

    #include <jni.h>
    #include <stdio.h>
    #include "TestJNIReference.h"
     
    // Global Reference to the Java class "java.lang.Integer"
    static jclass classInteger;
    static jmethodID midIntegerInit;
     
    jobject getInteger(JNIEnv *env, jobject thisObj, jint number) {
     
       // Get a class reference for java.lang.Integer if missing
       if (NULL == classInteger) {
          printf("Find java.lang.Integer\n");
          classInteger = (*env)->FindClass(env, "java/lang/Integer");
       }
       if (NULL == classInteger) return NULL;
     
       // Get the Method ID of the Integer's constructor if missing
       if (NULL == midIntegerInit) {
          printf("Get Method ID for java.lang.Integer's constructor\n");
          midIntegerInit = (*env)->GetMethodID(env, classInteger, "<init>", "(I)V");
       }
       if (NULL == midIntegerInit) return NULL;
     
       // Call back constructor to allocate a new instance, with an int argument
       jobject newObj = (*env)->NewObject(env, classInteger, midIntegerInit, number);
       printf("In C, constructed java.lang.Integer with number %d\n", number);
       return newObj;
    }
     
    JNIEXPORT jobject JNICALL Java_TestJNIReference_getIntegerObject
              (JNIEnv *env, jobject thisObj, jint number) {
       return getInteger(env, thisObj, number);
    }
     
    JNIEXPORT jobject JNICALL Java_TestJNIReference_anotherGetIntegerObject
              (JNIEnv *env, jobject thisObj, jint number) {
       return getInteger(env, thisObj, number);
    }
    

    在第二次调用时,java.lang.Integer的引用失效了(并且不是NULL),这是因为FindClass()返回的是一个局部引用,方法一旦结束就失效了。

    为了解决这个问题,我们需要创建一个全局引用:

       // Get a class reference for java.lang.Integer if missing
       if (NULL == classInteger) {
          printf("Find java.lang.Integer\n");
          // FindClass returns a local reference
          jclass classIntegerLocal = (*env)->FindClass(env, "java/lang/Integer");
          // Create a global reference from the local reference
          classInteger = (*env)->NewGlobalRef(env, classIntegerLocal);
          // No longer need the local reference, free it!
          (*env)->DeleteLocalRef(env, classIntegerLocal);
       }
    

    需要注意的是jmethodID和jfieldID不是jobject,无法创建全局引用。

    9. jni的常见错误

    错误信息:SEVERE: java.lang.UnsatisfiedLinkError: no xxx in java.library.path

    原因:使用了第三方的本地库

    结局方法:将本地库的路径添加到"java.library.path",使用如下命令(以JOGL为例):

    > java -Djava.library.path=d:\bin\jogl2.0\lib myjoglapp
    

    源码地址:关注下面的公众号回复“JNI一文详解”可获得源码地址。

    image

    微信公众号 长夜西风

    个人网站 http://www.cmder.info/

    相关文章

      网友评论

          本文标题:JNI一文详解

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