JNI在處理基本類型數組和對象數組上面是不同的。對象數組里面是一些指向對象實例或者其它數組的引用。
基本類型數組:
獲取數組元素指針的對應關系:
函數 數組類型
GetBooleanArrayElements boolean
GetByteArrayElements byte
GetCharArrayElements char
GetShortArrayElements short
GetIntArrayElements int
GetLongArrayElements long
GetFloatArrayElements float
GetDoubleArrayElements double
釋放數組元素指針的對應關系:
Function Array Type
ReleaseBooleanArrayElements boolean
ReleaseByteArrayElements byte
ReleaseCharArrayElements char
ReleaseShortArrayElements short
ReleaseIntArrayElements int
ReleaseLongArrayElements long
ReleaseFloatArrayElements float
ReleaseDoubleArrayElements double
看一個簡單的例子。下面的程序調用了一個本地方法 sumArray,這個方法對一個 int數組里面的元素進行累加:
class IntArray { private native int sumArray(int[] arr); public static void main(String[] args) { IntArray p = new IntArray(); int arr[] = new int[10]; for (int i = 0; i < 10; i++) { arr[i] = i; } int sum = p.sumArray(arr); System.out.println("sum = " + sum); } static { System.loadLibrary("IntArray"); } }
數組的引用類型是一般是jarray或者jarray的子類型jintArray。就像jstring不是一個C字符串類型一樣,jarray也不是一個C數組類型。
所以,不要直接訪問 jarray。你必須使用合適的JNI函數來訪問基本數組元素:
使用GetIntArrayRegion 函數來把一個 int數組中的所有元素復制到一個C緩沖區中,然后我們在本地代碼中通過C緩沖區來訪問這些元素。
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr) { jint buf[10]; jint i, sum = 0; (*env)->GetIntArrayRegion(env, arr, 0, 10, buf); for (i = 0; i < 10; i++) { sum += buf[i]; } return sum; }
JNI支持一系列的Get/Release<Type>ArrayElement 函數,這些函數允許本地代碼獲取一個指向基本類型數組的元素的指針。
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr) { jint *carr; jint i, sum = 0; carr = (*env)->GetIntArrayElements(env, arr, NULL); //推薦使用 if (carr == NULL) { return 0; /* exception occurred */ } for (i=0; i<10; i++) { sum += carr[i]; } (*env)->ReleaseIntArrayElements(env, arr, carr, 0); return sum; }
如果你想在一個預先分配的C緩沖區和內存之間交換數據,應該使用Get/Set</Type>ArrayRegion系列函數。這些函數會進行越界檢查,在需要的時候會有可能拋出ArrayIndexOutOfBoundsException異常。
對於少量的、固定大小的數組,Get/Set<Type>ArrayRegion是最好的選擇,因為C緩沖區可以在Stack(棧)上被很快地分配,而且復制少量數組元素的代價是很小的。這對函數的另外一個優點就是,允許你通過傳入一個索引和長度來實現對子字符串的操作。
如果你沒有一個預先分配的 C 緩沖區,並且原始數組長度未定,而本地代碼又不想在獲取數組元素的指針時阻塞的話,使用 Get/ReleasePrimitiveArrayCritical 函數對。就像Get/ReleaseStringCritical函數對一樣,這對函數很小心地使用,以避免死鎖。
Get/Release<type>ArrayElements 系列函數永遠是安全的。JVM 會選擇性地返回一個指針,這個指針可能指向原始數據也可能指向原始數據復制。
對象數組:
JNI提供了一個函數對來訪問對象數組。GetObjectArrayElement返回數組中指定位置的元素,而SetObjectArrayElement修改數組中指定位置的元素。
與基本類型的數組不同的是,你不能一次得到所有的對象元素或者一次復制多個對象元素。
字符串和數組都是引用類型,你要使用Get/SetObjectArrayElement來訪問字符串數組或者數組的數組。
下面的例子調用了一個本地方法來創建一個二維的 int數組,然后打印這個數組的內容:
class ObjectArrayTest { private static native int[][] initInt2DArray(int size); public static void main(String[] args) { int[][] i2arr = initInt2DArray(3); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { System.out.print(" " + i2arr[i][j]); } System.out.println(); } } static { System.loadLibrary("ObjectArrayTest"); } }
靜態本地方法 initInt2DArray 創建了一個給定大小的二維數組。執行分配和初始化數組任務的本地方法可以是下面這樣子的:
JNIEXPORT jobjectArray JNICALL Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size) { jobjectArray result; int i; jclass intArrCls = (*env)->FindClass(env, "[I"); if (intArrCls == NULL) { return NULL; /* exception thrown */ } result = (*env)->NewObjectArray(env, size, intArrCls, NULL); //分配第一維 if (result == NULL) { return NULL; /* out of memory error thrown */ } for (i = 0; i < size; i++) { jint tmp[256]; /* make sure it is large enough! */ int j; jintArray iarr = (*env)->NewIntArray(env, size); //創建第二維數據 if (iarr == NULL) { return NULL; /* out of memory error thrown */ } for (j = 0; j < size; j++) { tmp[j] = i + j; } (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp); (*env)->SetObjectArrayElement(env, result, i, iarr); (*env)->DeleteLocalRef(env, iarr); } return result; }
