JNI 學(xué)習(xí)筆記

JNI 學(xué)習(xí)筆記

1.概述

Java Native Interface(JNI) 是JDK提供的一個(gè)native編程接口。JNI 允許Java程序調(diào)用其他語(yǔ)言編寫(xiě)的程序或者代碼庫(kù), 比如C/C++。Java 在內(nèi)存管理和性能上有一定的局限,通過(guò)JNI我們就可以利用Native程序來(lái)克服這些限制。

2.一個(gè)簡(jiǎn)單的demo

通過(guò)一個(gè)簡(jiǎn)單的Demo我們來(lái)看一下Native 與 Java 的互相調(diào)用。

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

執(zhí)行 javac HelloJNI.java 編譯java 文件

C:

創(chuàng)建頭文件:HelloJNI.h, 可以通過(guò)javah 工具自動(dòng)生成:

執(zhí)行:>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 程序?qū)崿F(xiàn):

#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!\n");
   return;
}

簡(jiǎn)單解析:

Java 調(diào)用Native , 需要:

  • 通過(guò)System.loadLibrary("hello") 方法加載native 模塊"Hello"(該模塊包含了syHello()方法)。
  • 聲明native 方法 : private native void sayHello();
  • 在main方法中調(diào)用

C/C++ 需要實(shí)現(xiàn):

  • 定義頭文件, 可以通過(guò)javah自動(dòng)生成: javah HelloJNI , 我們可以看到對(duì)應(yīng)java中定義的sayHello()方法, 在頭文件中生成了JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);方法。其格式可以概括為JNIEXPORT <返回類(lèi)型> JNICALL Java_<包名>_<類(lèi)名>_<方法名>(JNIEnv *, jobject,<參數(shù)>); 包名中的(.)用下劃線(xiàn)(_)代替。 參數(shù)中JNIEnv * 是JNI環(huán)境, 提供了許多方法, 后面我們會(huì)提到。 jobject是調(diào)用的java對(duì)象。
  • 在C/C++文件中實(shí)現(xiàn)具體方法

3.JNI 基礎(chǔ)

3.1 JNI類(lèi)型

JNI 定義了以下類(lèi)型來(lái)對(duì)應(yīng)Java中的類(lèi)型:

a. 基本類(lèi)型: jni -> java

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

b. 引用類(lèi)型:

  • jclass --> java.lang.Class
  • jstring --> java.lang.String
  • jthrowable --> java.lang.Throwable
  • jarray --> Java數(shù)組

3.2參數(shù)傳遞

3.2.1. Java 基本類(lèi)型傳遞

對(duì)于Java 的基本類(lèi)型, 我們可以直接傳遞參數(shù), 在原生系統(tǒng)中已經(jīng)定義了jxxx中的基本類(lèi)型, 比如 jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean 對(duì)應(yīng) Java中的 int, byte, short, long, float, double, char, boolean

jni.hwin/jni_mh定義了以上8種JNI環(huán)境下的基本類(lèi)型, 除此之外還額外定義了jsize

// 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 對(duì)應(yīng)的是long類(lèi)型(至少是32位的),在C語(yǔ)言的基本類(lèi)型中, int 可能是只有16位, 這樣就無(wú)法對(duì)應(yīng)Java中的int類(lèi)型了。 所以為保險(xiǎn)起見(jiàn), JNI編程中建議在C/C++程序中使用jint代替int.

3.2.2. String 類(lèi)型傳遞

JNI 中定義了 jstring 類(lèi)型代替Java中的String 類(lèi)型,String 類(lèi)型的傳遞相比基本類(lèi)型要復(fù)雜, 因?yàn)樵贘ava中String 是個(gè)對(duì)象, 而在c中, string 是個(gè) char類(lèi)型數(shù)組。所以在傳遞String類(lèi)型的時(shí)候, 在String(被jstring替換) 類(lèi)型和 (char*)類(lèi)型之間做轉(zhuǎn)化。

JNI環(huán)境提供了一些轉(zhuǎn)換方法, 可以通過(guò)JNIEnv* 參數(shù)調(diào)用這些轉(zhuǎn)化方法:

  • 從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個(gè)字節(jié))編碼的string 類(lèi)型轉(zhuǎn)換, utf-8 編碼的string 和 C-string(char*)的行為類(lèi)似, 在 C/C++程序中使用。

UTF-8 strings的轉(zhuǎn)換方法

// 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() 方法可以用于通過(guò) jstring 創(chuàng)建一個(gè) C-string (char*), 如果內(nèi)存無(wú)法分配, 則返回NULL。

其中第三個(gè)參數(shù)isCopy 如果設(shè)為JNI_TRUE , 則返回結(jié)果是原始Java String 的拷貝, 如果設(shè)為JNI_FALSE, 則直接返回 Java String 的地址, 然而在這種情況下, 無(wú)法對(duì)string內(nèi)容進(jìn)行修改。JNI在運(yùn)行時(shí)會(huì)試圖返回指針, 如果可以的話(huà),否則會(huì)返回一個(gè)拷貝。 通常情況下, 我們并不關(guān)心底層string 的內(nèi)容呢, 所以通常都設(shè)為 NULL。

當(dāng)你不需要使用該方法返回的結(jié)果時(shí), 可以調(diào)用 ReleaseStringUTFChars() 來(lái)釋放內(nèi)存以及引用。

  • NewStringUTF() 方法則是通過(guò)c-string 創(chuàng)建一個(gè) 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

對(duì)與Unicode 編碼的類(lèi)型, 使用jchar* 代替 char* 存儲(chǔ)字符。

3.2.3 基本類(lèi)型的數(shù)組傳遞

在Java中, 數(shù)組是一個(gè)引用類(lèi)型, 像一個(gè)類(lèi)。 Java 數(shù)組有9種, 除了8種基本類(lèi)型的數(shù)組外, 還有一類(lèi)對(duì)象數(shù)組, 即java.lang.Object類(lèi)型的數(shù)組。 在JNI 中定義了 8種基本類(lèi)型的數(shù)組對(duì)應(yīng)Java 的8種基本類(lèi)型數(shù)組,jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray , 和一種對(duì)象數(shù)組jobjectArray對(duì)應(yīng)Java中的對(duì)象數(shù)組。

因此, 你需要在數(shù)組傳遞是處理JNI 數(shù)組和Native數(shù)組之間的轉(zhuǎn)換, 比如 jintArray <-> jint[], jdoubleArray <-> jdouble[] 等。 JNI 環(huán)境已經(jīng)提供了一些轉(zhuǎn)換方法, 以jintArray為例:

    1. jintArray(JNI) --> (Native)jint[] : jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)
    1. jint[] --> jintArray : 調(diào)用 jintArray NewIntArray(JNIEnv *env, jsize len) 分配內(nèi)存, 然后調(diào)用 SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)jin[] 拷貝到 jintArray

JNI 中有8組上述方法, 分別對(duì)應(yīng)8中基本類(lèi)型:

// 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() 用于根據(jù)jxxxArray創(chuàng)建 jxxx[]
  • GET|Set<PrimitiveType>ArrayRegion() 可以用于拷貝一個(gè)jxxxArray(或者其中一部分)到一個(gè) 預(yù)分配(pre-allocated)存儲(chǔ)的 jxxx[]
  • New<PrimitiveType>Array() 用于為jxxxArray分配內(nèi)存, 然后調(diào)用 Set<PrimitiveType>ArrayRegion() 方法 將jxxx[] 設(shè)值。
  • Get|ReleasePrimitiveArrayCritical() 則是在get 和 release周期之間, 不允許阻塞調(diào)用(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個(gè)步驟:

  • Step1: 將傳入的jxxxArray 轉(zhuǎn)換成 Native 的jxxx[]
  • Step2: 對(duì)jxxx[]進(jìn)行必要處理, 實(shí)現(xiàn)期望的功能
  • Step3:將Native結(jié)果 轉(zhuǎn)換成 jxxxArray返回

4. 訪(fǎng)問(wèn)Java對(duì)象中的成員和回調(diào)方法

4.1 訪(fǎng)問(wèn)對(duì)象中的成員變量

JNI 環(huán)境提供了一些訪(fǎng)問(wèn)成員變量的方法如下:

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.

通過(guò)以上方法,我們就可以實(shí)現(xiàn)在Native代碼中訪(fǎng)問(wèn)Java對(duì)象的成員變量了, 具體實(shí)現(xiàn)可以概括為以下幾個(gè)步驟:

  • Step1: 通過(guò)GetObjectClass() 方法獲取該對(duì)象的類(lèi)的引用;
  • Step2: 通過(guò)GetFieldID() 方法從類(lèi)引用中(Step1得到該引用)獲取FieldID; 調(diào)用該方法需要傳入成員變量的名稱(chēng)和對(duì)應(yīng)field的描述(descriptor)(或者簽名(signature))。描述的內(nèi)容具體如下:
    • 對(duì)于一個(gè)Java類(lèi)而言, Field 的描述格式為"L<fully-qualified-name>;", 以(/) 代替包名中的(.)。比如String 類(lèi)型的descrtptor為"Ljava/lang/String;"。(分號(hào)不能漏?。?!)
    • 對(duì)于基本類(lèi)型, "I" --> int, "B" --> byte, "S" --> short, "J" --> long, "F" --> float, "D" --> double, "C" --> char, "Z" --> boolean.
    • 對(duì)于數(shù)組而言, 對(duì)象數(shù)組以"["作為前綴, 比如"[Ljava/lang/Object;"為一個(gè)Object數(shù)組的描述,[I 為int的描述。
  • 基于FieldID, 我們可以通過(guò)GetObjectField() 或者Get<primitive-type>Field() 方法訪(fǎng)問(wèn)實(shí)例的成員變量。
  • 更新實(shí)例中成員變量的值, 可以通過(guò)SetObjectField() 或者 Set<primitive-type>Field() 來(lái)修改內(nèi)容, 這里需要傳入?yún)?shù)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\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);
}

4.2 訪(fǎng)問(wèn)類(lèi)的靜態(tài)變量

訪(fǎng)問(wèn)靜態(tài)變量與訪(fǎng)問(wèn)成員變量類(lèi)似, 只是調(diào)用的方法不同, 比如 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\n", number);
   number = 77.88;
   (*env)->SetStaticDoubleField(env, cls, fidNumber, number);
}

4.3 Native回調(diào)Java成員方法和靜態(tài)方法

Native 回調(diào) Java實(shí)例成員方法

  • Step1: 通過(guò)GetObjectClass() 獲取類(lèi)的引用
  • Step2: 從引用中獲取 MethodID, 通過(guò)調(diào)用GetMethodID(). 這里需要傳入方法名和簽名。 簽名的格式為"(parameters)return-type".,即(參數(shù)類(lèi)型...)返回類(lèi)型。 你可以使用javap工具列出一個(gè)類(lèi)中方法的簽名,-s 打印簽名, -p 現(xiàn)實(shí)私有方法:
> 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 , 就可以通過(guò)調(diào)用 Call<Primitive-type>Method() or CallVoidMethod() or CallObjectMethod()方法回調(diào)Java實(shí)例中的成員方法。

回調(diào)靜態(tài)的方法步驟與毀掉成員方法類(lèi)似, 只是方法不同。 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()\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);
}

4.4 回調(diào)重寫(xiě)父類(lèi)的成員方法

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

具體回調(diào)實(shí)現(xiàn)與普通成員方法一樣, 只是調(diào)用JNI提供的不同方法。

5 創(chuàng)建對(duì)象和對(duì)象數(shù)組

通過(guò)NewObject 方法和newObjectArray()方法, 我們可以在Native中創(chuàng)建jobjectjobjectArray, 并將其返回給Java程序。

5.1 在Native程序中回調(diào)Java程序的構(gòu)造函數(shù)來(lái)創(chuàng)建一個(gè)Java對(duì)象

回調(diào)構(gòu)造方法和回調(diào)成員方法類(lèi)似。首先獲取構(gòu)造函數(shù)的Method ID. 方法名為"<init>", 返回類(lèi)型為"V". 然后就可以通過(guò)調(diào)用NewObject() 方法調(diào)用構(gòu)造函數(shù)來(lái)構(gòu)建一個(gè)java對(duì)象。

JNI 提供的創(chuàng)建對(duì)象(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\n", resultCStr);
 
   return newObj;
}


5.2 對(duì)象數(shù)組

與基本類(lèi)型的數(shù)組不同, 對(duì)象數(shù)組不能批量處理, 需要通過(guò)Get|SetObjectArrayElement() 方法一一處理。

JNI 提供創(chuàng)建和操作對(duì)象數(shù)組的方法如下:

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\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 將對(duì)象引用(針對(duì)jobject)根據(jù)Native中的使用情況分為兩類(lèi): 本地引用(local)和全局引用(global):

    1. 本地引用在Native方法中創(chuàng)建, 方法結(jié)束時(shí)釋放, 僅在方法內(nèi)有效。當(dāng)然也可以直接調(diào)用DeleteLocalRef() 方法使本地引用失效, 這樣就可以馬上GC了。 所有Java 程序傳遞到Native方法的引用都是本地引用。 所有JNI 方法返回的jobject也是本地引用。
    1. 一個(gè)全局引用在程序員將它釋放之前會(huì)一直存在。JNI 提供的釋放方法為DeleteGlobalRef()。同時(shí), JNI 也提供了通過(guò)本地引用創(chuàng)建全局引用的方法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\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);
}

在以上程序中, 我們通過(guò)FindClass()方法獲取java.lang.Integer類(lèi)的引用, 并將它存儲(chǔ)到全局的靜態(tài)變量中。然而, 在下一次調(diào)用是, 這個(gè)引用將不可用(而且不為NULL), 因?yàn)?code>FindClass() 返回的是一個(gè)局部變量, 當(dāng)方法結(jié)束是就會(huì)失效。

為了解決這個(gè)問(wèn)題, 我們需要通過(guò)FindClass()返回的引用創(chuàng)建一個(gè)全局引用:

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

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容