zoukankan      html  css  js  c++  java
  • JNI 学习笔记

    https://baike.baidu.com/item/JNI/9412164?fr=aladdin

    https://www.jianshu.com/p/f6e3dd8edb13

    96 
    JellyJoe_943 
    2016.09.27 18:58* 字数 2769 阅读 1673评论 1

    JNI 学习笔记

    1.概述

    Java Native Interface(JNI) 是JDK提供的一个native编程接口。JNI 允许Java程序调用其他语言编写的程序或者代码库, 比如C/C++。Java 在内存管理和性能上有一定的局限,通过JNI我们就可以利用Native程序来克服这些限制。

    2.一个简单的demo

    通过一个简单的Demo我们来看一下Native 与 Java 的互相调用。

    Java :HelloJNI.java

    public class HelloJNI {
       static {
          System.loadLibrary("hello"); // Load native library at runtime
                                       // hello.dll (Windows) or libhello.so (Unixes)
       }
     
       // Declare a native method sayHello() that receives nothing and returns void
       private native void sayHello();
     
       // Test Driver
       public static void main(String[] args) {
          new HelloJNI().sayHello();  // invoke the native method
       }
    }
    

    执行 javac HelloJNI.java 编译java 文件

    C:

    创建头文件:HelloJNI.h, 可以通过javah 工具自动生成:

    执行:>javah HelloJNI 这里HelloJNI指的是编译后的.class文件

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for 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
    

    C 程序实现:

    #include <jni.h>
    #include <stdio.h>
    #include "HelloJNI.h"
     
    // Implementation of native method sayHello() of HelloJNI class
    JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
       printf("Hello World!
    ");
       return;
    }
    

    简单解析:

    Java 调用Native , 需要:

    • 通过System.loadLibrary("hello") 方法加载native 模块"Hello"(该模块包含了syHello()方法)。
    • 声明native 方法 : private native void sayHello();
    • 在main方法中调用

    C/C++ 需要实现:

    • 定义头文件, 可以通过javah自动生成: javah HelloJNI , 我们可以看到对应java中定义的sayHello()方法, 在头文件中生成了JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);方法。其格式可以概括为JNIEXPORT <返回类型> JNICALL Java_<包名>_<类名>_<方法名>(JNIEnv *, jobject,<参数>); 包名中的(.)用下划线(_)代替。 参数中JNIEnv * 是JNI环境, 提供了许多方法, 后面我们会提到。 jobject是调用的java对象。
    • 在C/C++文件中实现具体方法

    3.JNI 基础

    3.1 JNI类型

    JNI 定义了以下类型来对应Java中的类型:

    a. 基本类型: jni -> java

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

    b. 引用类型:

    • jclass --> java.lang.Class
    • jstring --> java.lang.String
    • jthrowable --> java.lang.Throwable
    • jarray --> Java数组

    3.2参数传递

    3.2.1. Java 基本类型传递

    对于Java 的基本类型, 我们可以直接传递参数, 在原生系统中已经定义了jxxx中的基本类型, 比如 jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean 对应 Java中的 int, byte, short, long, float, double, char, boolean

    jni.h 和 win/jni_mh定义了以上8种JNI环境下的基本类型, 除此之外还额外定义了jsize

    // In "winjni_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 对应的是long类型(至少是32位的),在C语言的基本类型中, int 可能是只有16位, 这样就无法对应Java中的int类型了。 所以为保险起见, JNI编程中建议在C/C++程序中使用jint代替int.

    3.2.2. String 类型传递

    JNI 中定义了 jstring 类型代替Java中的String 类型,String 类型的传递相比基本类型要复杂, 因为在Java中String 是个对象, 而在c中, string 是个 char类型数组。所以在传递String类型的时候, 在String(被jstring替换) 类型和 (char*)类型之间做转化。

    JNI环境提供了一些转换方法, 可以通过JNIEnv* 参数调用这些转化方法:

    • 从jstring 中获取c-string, 即char* : const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)
    • 从C-string(char*) 中获取jstring : jstring NewStringUTF(JNIEnv*, char*)

    JNI Native String Functions

    JNI 支持unicode(16位)编码和utf-8(1-3个字节)编码的string 类型转换, utf-8 编码的string 和 C-string(char*)的行为类似, 在 C/C++程序中使用。

    UTF-8 strings的转换方法

    // 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.
    
    
    
    • GetStringUTFChars() 方法可以用于通过 jstring 创建一个 C-string (char*), 如果内存无法分配, 则返回NULL。

    其中第三个参数isCopy 如果设为JNI_TRUE , 则返回结果是原始Java String 的拷贝, 如果设为JNI_FALSE, 则直接返回 Java String 的地址, 然而在这种情况下, 无法对string内容进行修改。JNI在运行时会试图返回指针, 如果可以的话,否则会返回一个拷贝。 通常情况下, 我们并不关心底层string 的内容呢, 所以通常都设为 NULL。

    当你不需要使用该方法返回的结果时, 可以调用 ReleaseStringUTFChars() 来释放内存以及引用。

    • NewStringUTF() 方法则是通过c-string 创建一个 JNI String (jstring)

    想要了解更多可以查看: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/index.html.

    Unicode String

      
    // 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
    
    

    对与Unicode 编码的类型, 使用jchar* 代替 char* 存储字符。

    3.2.3 基本类型的数组传递

    在Java中, 数组是一个引用类型, 像一个类。 Java 数组有9种, 除了8种基本类型的数组外, 还有一类对象数组, 即java.lang.Object类型的数组。 在JNI 中定义了 8种基本类型的数组对应Java 的8种基本类型数组,jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray , 和一种对象数组jobjectArray对应Java中的对象数组。

    因此, 你需要在数组传递是处理JNI 数组和Native数组之间的转换, 比如 jintArray <-> jint[], jdoubleArray <-> jdouble[] 等。 JNI 环境已经提供了一些转换方法, 以jintArray为例:

      1. jintArray(JNI) --> (Native)jint[] : jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)
      1. jint[] --> jintArray : 调用 jintArray NewIntArray(JNIEnv *env, jsize len) 分配内存, 然后调用 SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf) 将jin[] 拷贝到 jintArray

    JNI 中有8组上述方法, 分别对应8中基本类型:

    // 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() 用于根据jxxxArray创建 jxxx[]
    • GET|Set<PrimitiveType>ArrayRegion() 可以用于拷贝一个jxxxArray(或者其中一部分)到一个 预分配(pre-allocated)存储的 jxxx[]
    • New<PrimitiveType>Array() 用于为jxxxArray分配内存, 然后调用 Set<PrimitiveType>ArrayRegion() 方法 将jxxx[] 设值。
    • Get|ReleasePrimitiveArrayCritical() 则是在get 和 release周期之间, 不允许阻塞调用(blocking calls)。

    Example:

    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]);
       }
    }
    
    

    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;
    }
    
    

    Native程序的处理可以概括为3个步骤:

    • Step1: 将传入的jxxxArray 转换成 Native 的jxxx[]
    • Step2: 对jxxx[]进行必要处理, 实现期望的功能
    • Step3:将Native结果 转换成 jxxxArray返回

    4. 访问Java对象中的成员和回调方法

    4.1 访问对象中的成员变量

    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.
    
    

    通过以上方法,我们就可以实现在Native代码中访问Java对象的成员变量了, 具体实现可以概括为以下几个步骤:

    • Step1: 通过GetObjectClass() 方法获取该对象的类的引用;
    • Step2: 通过GetFieldID() 方法从类引用中(Step1得到该引用)获取FieldID; 调用该方法需要传入成员变量的名称和对应field的描述(descriptor)(或者签名(signature))。描述的内容具体如下:
      • 对于一个Java类而言, Field 的描述格式为"L<fully-qualified-name>;", 以(/) 代替包名中的(.)。比如String 类型的descrtptor为"Ljava/lang/String;"。(分号不能漏!!!)
      • 对于基本类型, "I" --> int, "B" --> byte, "S" --> short, "J" --> long, "F" --> float, "D" --> double, "C" --> char, "Z" --> boolean.
      • 对于数组而言, 对象数组以"["作为前缀, 比如"[Ljava/lang/Object;"为一个Object数组的描述,[I 为int的描述。
    • 基于FieldID, 我们可以通过GetObjectField() 或者Get<primitive-type>Field() 方法访问实例的成员变量。
    • 更新实例中成员变量的值, 可以通过SetObjectField() 或者 Set<primitive-type>Field() 来修改内容, 这里需要传入参数FieldID.

    Example:

    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);
       }
    }
    
    

    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
    ", 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
    ", 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);
    }
    
    

    4.2 访问类的静态变量

    访问静态变量与访问成员变量类似, 只是调用的方法不同, 比如 GetStaticFieldID(), Get|SetStaticObjectField(), Get|SetStatic<Primitive-type>Field() :

    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.
    

    Example:

    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);
       }
    }
    

    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
    ", number);
       number = 77.88;
       (*env)->SetStaticDoubleField(env, cls, fidNumber, number);
    }
    
    

    4.3 Native回调Java成员方法和静态方法

    Native 回调 Java实例成员方法

    • Step1: 通过GetObjectClass() 获取类的引用
    • Step2: 从引用中获取 MethodID, 通过调用GetMethodID(). 这里需要传入方法名和签名。 签名的格式为"(parameters)return-type".,即(参数类型...)返回类型。 你可以使用javap工具列出一个类中方法的签名,-s 打印签名, -p 现实私有方法:
    > 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() or CallVoidMethod() or CallObjectMethod()方法回调Java实例中的成员方法。

    回调静态的方法步骤与毁掉成员方法类似, 只是方法不同。 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.
    
    

    Example:

    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();
       }
    }
    
    

    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()
    ");
       // 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)
    ");
       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
    ", 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
    ", resultCStr);
       (*env)->ReleaseStringUTFChars(env, resultJNIStr, resultCStr);
    }
    
    

    4.4 回调重写父类的成员方法

    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);
    
    

    具体回调实现与普通成员方法一样, 只是调用JNI提供的不同方法。

    5 创建对象和对象数组

    通过NewObject 方法和newObjectArray()方法, 我们可以在Native中创建jobject和 jobjectArray, 并将其返回给Java程序。

    5.1 在Native程序中回调Java程序的构造函数来创建一个Java对象

    回调构造方法和回调成员方法类似。首先获取构造函数的Method ID. 方法名为"<init>", 返回类型为"V". 然后就可以通过调用NewObject() 方法调用构造函数来构建一个java对象。

    JNI 提供的创建对象(jobject)的方法如下:

    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.
    
    

    Example:

    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));
       }
    }
    

    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 runnning 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
    ", resultCStr);
     
       return newObj;
    }
    
    
    

    5.2 对象数组

    与基本类型的数组不同, 对象数组不能批量处理, 需要通过Get|SetObjectArrayElement() 方法一一处理。

    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.
    
    

    Example

    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]);
       }
    }
    

    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
    ", sum);
       printf("In C, the average is %f
    ", 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 将对象引用(针对jobject)根据Native中的使用情况分为两类: 本地引用(local)和全局引用(global):

      1. 本地引用在Native方法中创建, 方法结束时释放, 仅在方法内有效。当然也可以直接调用DeleteLocalRef() 方法使本地引用失效, 这样就可以马上GC了。 所有Java 程序传递到Native方法的引用都是本地引用。 所有JNI 方法返回的jobject也是本地引用。
      1. 一个全局引用在程序员将它释放之前会一直存在。JNI 提供的释放方法为DeleteGlobalRef()。同时, JNI 也提供了通过本地引用创建全局引用的方法NewGlobalRef()

    Example

    Java:

    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:

    #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
    ");
          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
    ");
          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
    ", 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);
    }
    
    

    在以上程序中, 我们通过FindClass()方法获取java.lang.Integer类的引用, 并将它存储到全局的静态变量中。然而, 在下一次调用是, 这个引用将不可用(而且不为NULL), 因为FindClass() 返回的是一个局部变量, 当方法结束是就会失效。

    为了解决这个问题, 我们需要通过FindClass()返回的引用创建一个全局引用:

       // Get a class reference for java.lang.Integer if missing
       if (NULL == classInteger) {
          printf("Find java.lang.Integer
    ");
          // 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);
       }
    
    

    参考资料

  • 相关阅读:
    rgb随机颜色函数
    mapshaper转geojson
    postgis
    Draw
    ol 聚类ol.source.Cluster的使用
    ol ---- overlay autoPan的使用
    多层数据注入同一个图层源时,要批量删除某一种要素
    js遍历数组,并从数组中删除元素
    echarts加载geojson
    centos65编译安装lamp和lnmp
  • 原文地址:https://www.cnblogs.com/kelelipeng/p/10368963.html
Copyright © 2011-2022 走看看