JNI介面實現Java和C的互動
當面對帶有原生代碼的 Java 的應用程式時,程式設計師問的最通常的問之一,是在 Java 程式語言中的資料型別怎樣對映到本地程式語言C和C++中的資料型別。實際上,大多數程式將需要傳遞引數給本地方法,和也從本地方法接受結果。
1、基本型別的對映
在本地方法宣告中引數型別有對應的在本地程式語言中的型別。 JNI定義了一套C和C++型別來對應在Java程式語言中的型別。
在Java程式語言中的兩種型別:基本來型如int,float,和char和參考型別如classes,instances和arrays.在Java程式語言中, strings 是java.lang.String類的一個例項。
JNI不同地對待基本型別和參考型別。基本型別的對映是簡單易懂的。例如,在Java程式語言中的int型別對映到C/C++的jint型別(定義在jni.h作為一個有符號 32bit 整型),同時在Java程式語言中的float類對映到C++的jfloat型別(定義在jni.h作為一個有符號 32bit浮點數)。基本型別都有它們對應的表述:
JNI傳遞objects到本地方法作為不透明的引用(opaque references)。 不透明的引用是一個 C 指標型別,引用了在 Java 虛擬機器中的內部資料結構。然而,內部資料結構的精確安排,對程式設計者是隱藏的。原生代碼必須通過恰當的JNI函式處理下面的物件(objects), JNI函式通過JNIEnv介面指標是可用的。例如,為java.lang.String對應的JNI型別是jstring。一個jstring引用(reference)的確切值是和原生代碼無關的。原生代碼呼叫JNI函式例如GetStringUTFChars來訪
問一個 string 的內容。
所有的JNI引用都是型別 jobject。為了方便和增強型別的安全, JNI定義了一組引用類,它們概念上為jobject的子型別(subtypes).( A 是 B 的子類, A 的例項也是 B 的例項。 )這些子類對應在Java程式語言中常用地引用型別。例如, jstring指示strings;jobjextArray指示一組objects。
jstring型別表示在 Java虛擬機器中的strings,同時和規定的“C string型別不同(指向字元的指標, char *)。本地方法程式碼必須使用恰當的JNI函式來轉化jstirng objects為C/C++ strings。 JNI支援轉換到或從Unicode和UTF-8的strings。
//java程式碼 package com.igood.ndk.hello; public class Prompt { //宣告載入的C庫中的本地方法 private native String getLine(String prompt) ; static { System.loadLibrary("Prompt") ; } }
//C語言實現的jni
#include <stdio.h>
#include <stdlib.h>
#include "com_igood_ndk_hello_Prompt.h"
JNIEXPORT jstring JNICALL Java_com_igood_ndk_hello_Prompt_getLine
(JNIEnv *env , jobject thiz, jstring prompt)
{
char buf[128] ;
const jbyte *str ;
str = (*env)->GetStringUTFChars(env, prompt , NULL) ;//java String物件轉換到本地字串
if (NULL == str){
return NULL;
}
printf("%s", str) ;
(*env)->ReleaseStringUTFChars(env, prompt, str) ;//釋放指向utf-8格式的char*的指標
scanf("%s", buf) ;
return (*env)->NewStringUTF(env, buf) ;//構建新的utf-8格式的字串
}
2.1、java String物件轉換到本地字串
JNI的函式GetStringUTFChars可以用來閱讀string的內容。通過JNIEnv的介面指標 GetStringUTFChars函式是能被呼叫的。它轉換了作為一個Unicode序列通過 Java 虛擬機器的實現來表示jstring的引用到用UTF8 格式表示的一個C string。
const char* GetStringUTFChars(JNIEnv* env, jstring str, jboolean*isCopy);
不要忘記檢查GetStringUTFChars的返回值。因為 Java 虛擬機器實現需要分配空間來儲存UTF-8string,這有有機會內配失敗。當這樣的事發生時,GetStringUTFChars返回NULL,同時通過JNI丟擲一個異常。
2.2、釋放本地字元資源
當你原生代碼結束使用通過GetStringUTFChars得到的UTF-8 string時,它呼叫ReleaseStringUTFChars。呼叫ReleaseStringUTFChars指明本地方法不再需要這GetStringUTFChars返回的UTF-8 string了;因此UTF-8 string佔有的記憶體將被釋放。沒有呼叫ReleaseStringUTFChars將導致記憶體洩漏,這將可能最終導致記憶體的耗盡。
void ReleaseStringUTFChars(JNIEnv*env, jstring str, const char* pchar);
2.3、構建新的字串
jstring NewStringUTF(JNIEnv*env, const char* pchar);
在本地方法中通過呼叫JNI函式NewStringUTF,你能構建一個新的java.lang.String例項。NewStringUTF函式使用一個帶有UTF-8格式的C string,同時構建一個java.lang.String例項。最新被構建的java.lang.String例項表現為和被給的UTF-8 C string一樣的Unicode字元序列。如果虛擬機器沒有記憶體分配來構建java.lang.String例項, NewStringUTF丟擲一個OutofMemoryError異常,同時返回NULL。
2.4、其他的JNI字串函式
JNI支援大量的其他字串相關的函式(string-related functions),除了前面介紹的GetStringUTFChars, ReleaseStringUTFChars和NewStringUTF函式。GetStringChars和ReleaseStringChars獲得字串使用Unicode格式。例如,在作業系統支援Unicode 為本地字串格式的時候,這些函式有用。UTF-8 string總是以\0字元結尾, Unicode 字元不是。為在一個jstring引用中得到 Unicode字元的個數, JNI 程式設計師能呼叫GetStringLength。為得到用UTF-8格式表示的一個jstring需要的bytes數,JNI程式設計師可以呼叫 ASCII C 函式 strlen 在GetStringUTFChars的結果上,或直接地呼叫JNI函式GetStringUTFLength在jstring引用上。
- GetStringUTFLengt獲取 UTF-8格式的char*的長度。
jsize GetStringUTFLength(JNIEnv*env, jstring str);
- GetStringChars將jstring轉換成為Unicode格式的char*。
const jchar* GetStringChars(JNIEnv*env, jstring str, jboolean*isCopy);
- ReleaseStringChars釋放指向Unicode格式的char*的指標。
void ReleaseStringChars(JNIEnv*env, jstring str, const jchar* pchar);
- NewString建立一個Unicode格式的String物件。
jstring NewString(JNIEnv*env, const jchar*pchar, jsize size);
- GetStringLength獲取Unicode格式的char*的長度。
jsize GetStringLength(JNIEnv*env, jstring str);
GetStringChars和GetStringUTFChar的第三個引數isCopy的需要額外的解釋:當來自GetStringChars返回時,通過isCopy被設定為JNI_TRUE,返回的字串是個在原始java.lang.String例項的字串的一個複製,記憶體定位指向它。通過isCopy被設定為JNI_FALSE,返回字串時一個直接指向在原始的java.lang.String例項中的字串,記憶體定位指向它。當通過isCopy被設定為JNI_FALSE,記憶體定位指向的字串時,原生代碼必須不能修改返回的字串的內容。違反了這個規則將引起原始java.lang.String例項也被修改。這破壞了java.lang.String永恆不可變性。
最常傳遞NULL作為isCopy引數,因為你不關心 Java 虛擬機器是否返回在java.lang.String例項中的一個複製字串和直接指向原始字串。
不要忘記呼叫ReleaseStringChars,當你不再需要訪問來自GetStringChars返回的string元素的時候。 ReleaseStringChars呼叫是必須的,無論GetStringChars設定* isCopy為JNI_TRUE或JNI_FALSE。 ReleaseStringChars釋放副本,或解除例項的固定,這依賴GetStringChars是返回一個副本還是指向例項。 3、訪問陣列
和String物件一樣,在本地方法 中不能直接訪問jarray物件,而是使用JNIEnv指標指向的一些方法來使用。JNI對待基本的陣列和物件陣列是不同地。基礎陣列包含原始是基本型別的例如int和boolean。物件陣列(Object arrays)包含元素是應用型別例如 class 例項和其他陣列。
3.1、訪問Java原始型別陣列:
1)獲取陣列的長度:
jsize GetArrayLength(JNIEnv*env, jarray arr);
這裡獲取陣列的長度和普通的c語言中的獲取陣列長度的函式不一樣,這裡使用JNIEvn的一個函式 GetArrayLength。2)獲取一個指向int陣列元素的指標:
jint* GetIntArrayElements(JNIEnv*env, jintArray arr, jboolean*isCopy);
使用 GetIntArrayElements方法獲取指向arr陣列元素的指標,注意該函式的引數,第一個引數是JNIEnv,第二個引數是陣列,第三個引數是是否通過拷貝原來陣列。
3)釋放陣列元素的引用
void ReleaseIntArrayElements(JNIEnv*env, jintArray arr,
jint* pArr, jint mode);
和操作String中的釋放String的引用是一樣的,提醒JVM回收arr陣列元素的引用。 這裡舉的例子是使用int陣列的,同樣還有boolean、float等對應的陣列。
獲取陣列元素指標的對應關係:
函式 |
Java 陣列型別 |
本地型別 |
GetBooleanArrayElements |
jbooleanArray |
jboolean |
GetByteArrayElements |
jbyteArray |
jbyte |
GetCharArrayElements |
jcharArray |
jchar |
GetShortArrayElements |
jshortArray |
jshort |
GetIntArrayElements |
jintArray |
jint |
GetLongArrayElements |
jlongArray |
jlong |
GetFloatArrayElements |
jfloatArray |
jfloat |
GetDoubleArrayElements |
jdoubleArray |
jdouble |
函式 |
Java 陣列型別 |
本地型別 |
ReleaseBooleanArrayElements |
jbooleanArray |
jboolean |
ReleaseByteArrayElements |
jbyteArray |
jbyte |
ReleaseCharArrayElements |
jcharArray |
jchar |
ReleaseShortArrayElements |
jshortArray |
jshort |
ReleaseIntArrayElements |
jintArray |
jint |
ReleaseLongArrayElements |
jlongArray |
jlong |
ReleaseFloatArrayElements |
jfloatArray |
jfloat |
ReleaseDoubleArrayElements |
jdoubleArray |
jdouble |
package com.igood.ndk.hello;
public class NativeClass {
//靜態程式碼塊在類載入時會執行,這個時候就會載入本地的C庫檔案
static{
//載入本地的C庫檔案
System.loadLibrary("helloAndroidNDK");
}
//宣告載入的C庫中的本地方法,獲得int型別陣列
public native int[] getIntegerArray(int length);
//宣告載入的C庫中的本地方法,設定int型別陣列,返回陣列元素的和
public native int setIntegerArray(int[] pIntArray);
}
//C標頭檔案
#include <jni.h>
/* Header for class com_igood_ndk_hello_NativeClass */
#ifndef _Included_com_igood_ndk_hello_NativeClass
#define _Included_com_igood_ndk_hello_NativeClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_igood_ndk_hello_NativeClass
* Method: getIntegerArray
* Signature: (I)[I
*/
JNIEXPORT jintArray JNICALL Java_com_igood_ndk_hello_NativeClass_getIntegerArray
(JNIEnv *, jobject, jint);
/*
* Class: com_igood_ndk_hello_NativeClass
* Method: setIntegerArray
* Signature: ([I)I
*/
JNIEXPORT jint JNICALL Java_com_igood_ndk_hello_NativeClass_setIntegerArray
(JNIEnv *, jobject, jintArray);
#ifdef __cplusplus
}
#endif
#endif
//C實現程式碼
#include <stdio.h>
#include <stdlib.h>
#include "com_igood_ndk_hello_NativeClass.h"
JNIEXPORT jintArray JNICALL Java_com_igood_ndk_hello_NativeClass_getIntegerArray
(JNIEnv *env, jobject thiz, jint length)
{
int i = 0;
jintArray outArray = (*env)->NewIntArray(env, length);
jint *a = (jint*)malloc(length*sizeof(jint));
if(a == NULL)
{
return NULL;
}
for (i = 0; i < length; i++) {
a[i] = i;
}
(*env)->SetIntArrayRegion(env,outArray,0,(jsize)length,a);
return outArray;
}
JNIEXPORT jint JNICALL Java_com_igood_ndk_hello_NativeClass_setIntegerArray
(JNIEnv *env, jobject thiz, jintArray inArr)
{
jint *jint_arr;
jboolean jbIsCopy = JNI_TRUE;
jint sum = 0;
int i = 0;
jint_arr = (*env)->GetIntArrayElements(env, inArr, &jbIsCopy);
if (jint_arr == NULL) {
return 0;
}
//獲取陣列的長度
jsize len = (*env)->GetArrayLength(env,inArr);
for(i = 0;i< len;i++)
{
sum += jint_arr[i];
}
(*env)->ReleaseIntArrayElements( env,inArr,jint_arr,0 );
return sum;
}
//android測試程式碼
package com.igood.ndk.hello;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView tvMsg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NativeClass nc = new NativeClass();
int []arr = nc.getIntegerArray(10);
for(int i = 0; i < 10;i++)
{
Log.d("TAG", "arr["+i+"]="+arr[i]);
}
int[] pIntArray = new int[20];
for(int i = 0;i<20;i++){
pIntArray[i] = i + 5;
}
Log.d("TAG", "setIntegerArray="+ nc.setIntegerArray(pIntArray));
}
}
執行結果: