這篇拙文將通過執行個體的方式來簡易學習JNI的資料類型。任何語言都有其基本的資料類型,要深入瞭解,必須要瞭解最基礎的東西,在JNI中,我們會問:Java語言中的資料類型是如何映射到C/C++本地語言中的呢?
目錄:
1. 一個簡單的執行個體分析
2. Java與JNI資料類型的映射
3. 字串的處理
4. 數組的處理
1. 一個簡單的執行個體分析編程中,向函數傳參和函數傳回值是很普遍的事情,這裡就通過幾個執行個體介紹這些技術。先通過一個簡單的執行個體來入門吧。這裡擴充HelloWorld.java,先列印一段字串,然後等待使用者輸入,看代碼:(這裡我只列出C的代碼)
[cpp]
<SPAN style="FONT-SIZE: 16px"> JNIEXPORT jstring JNICALL Java_org_winplus_basetype_HelloWorld_getLine (JNIEnv *, jobject, jstring);//(這串代碼怎麼來的,請看【Android應用開發】-(14)JNI----經典執行個體分析 )</SPAN>
JNIEXPORT jstring JNICALL Java_org_winplus_basetype_HelloWorld_getLine (JNIEnv *, jobject, jstring);//(這串代碼怎麼來的,請看【Android應用開發】-(14)JNI----經典執行個體分析 )
我們看getLine這個函數接收三個參數JNIEnv,jobject,jstring。其中JNIEnv包括JNI的函數表,如:
jobject的意義取決於該方法是靜態還是執行個體方法,當本地方法作為一個方法時,jobject相當於對象本身,即this。當本地方法作為一個靜態方法時,指向所在類。在本執行個體中,getline是一個本地執行個體方法實現,所以jobject指向對象本身。
2. 類型的映射
按資料類型來說,java中有兩種資料類型,基本的資料類型(int float,char),另一種是參考型別(執行個體、數組)。在本地方法(native 聲明的方法)中聲明的參數類型,在JNI中都有對應的類型。
Java與JNI基本類型的映射很直接,請看下錶:
Java
Native(jni.h)
boolean
jboolean
byte
jbyte
char
jchar
short
jshort
Int
jint
long
jlong
Float
jfloat
double
jdouble
除了這些類型之外還定義了jstring,jclass,jobjectArray等結構。他們都繼承自jobject.
相比基本類型,物件類型的傳遞要複雜很多。Java層的對象對象以(opaque references)指標的形勢傳遞到JNI層。opaque references是C的指標類型,它指向JavaVM內部資料結構。使用這種指標的目的是:不希望JNI使用者瞭解JAVAVM內部資料結構。對opaque references所指結構的操作,都要通過JNI方法進行。比如,”java.lang.String”對象,JNI層對於的類型為jstring,對該opaque references的操作要通過JNIEnv->GetStringUTFChars進行。這裡需要注意的是:編程的過程中一定要按照原則編程,千萬不要為了效率或者容易渠道某個值而繞過JNI,直接操作opaque references。
3. 字串處理
1) 執行個體
現在我們回到執行個體中,看如何處理從java傳過去的String類型,請看下面代碼?
[cpp]
JNIEXPORT jstring JNICALL Java_org_winplus_basetype_HelloWorld_getLine (JNIEnv *env, jobject obj, jstring prompt){
Printf(“%s”,prompt);
}
JNIEXPORT jstring JNICALL Java_org_winplus_basetype_HelloWorld_getLine (JNIEnv *env, jobject obj, jstring prompt){
Printf(“%s”,prompt);
}
很抱歉,出錯了:incorrect use of jstring as a char* pointer。
這裡需要使用對於JNI函數把jstring裝好為C/C++字串。JNI支援Unicode/UTF-8字元編碼互轉。Unicode 以16-bits值編碼;UTF-8是一種以位元組為單位變長格式的字元編碼,並與7-bits ASCII碼相容。UTF-8字串與C字串一樣,以NULL('\0')做結束符, 當UTF-8包含非ASCII 碼字元時,以'\0'做結束符的規則不變。7-bit ASCII字元的取值範圍在 1-127之間,這些 字元的範圍與UTF-8中相同。當最高位被設定時,表示多位元組編碼。如下,調用GetStringUTFChars,把一個Unicode字串轉成UTF-8格式字串,如果你確定字串只包含7-bit ASCII字元。這個字串可以使用C庫中的相關函數,如printf.
[cpp]
Java_org_winplus_basetype_HelloWorld_getLine (JNIEnv *env, jobject obj, jstring prompt){
char buf[128];
const jbyte *str;
str = (*env)->GetStringUTFChars(env, prompt, NULL);
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("%s , str);
(*env)->ReleaseStringUTFChars(env, prompt, str);
/* We assume here that the user does not type more than 127 characters */
scanf("%127s , buf);
return (*env)->NewStringUTF(env, buf);
}
Java_org_winplus_basetype_HelloWorld_getLine (JNIEnv *env, jobject obj, jstring prompt){
char buf[128];
const jbyte *str;
str = (*env)->GetStringUTFChars(env, prompt, NULL);
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("%s , str);
(*env)->ReleaseStringUTFChars(env, prompt, str);
/* We assume here that the user does not type more than 127 characters */
scanf("%127s , buf);
return (*env)->NewStringUTF(env, buf);
}
這裡一定要檢測GetStringUTFChars的傳回值,因為調用該函數會有記憶體配置操作。失敗有該函數返回NULL,並拋出OutOfMemoryError異常。發生異常不會改變代碼的執行軌跡,所以當返回Null時要及時處理異常。
1) 釋放GetStringUTFChars調用時的記憶體使用量。ReleaseStringUTFChars
2) 構造新的字串使用JNIEnv->NewStringUTF,同樣的,如果沒有足夠的記憶體也會拋出OutOfMemoryError異常。
3) 其他字串操作方法
GetStringChars是有Java 內部Unicode到本地UTF-8的轉換函式,可以調用 GetStringLength,獲得以Unicode編碼的字串長度。也可以使用strlen計算 GetStringUTFChars的傳回值,得到字串長度。
const jchar * GetStringChars(JNIEnv *env, jstring str, jboolean *isCopy);
具體的使用請參考JNI詳解16-20頁
4. 數組的操作
Java部分代碼很簡單,不貼出來了。我們直接看C代碼。
[cpp]
JNIEXPORT jint JNICALL Java_org_winplus_basetype_IntArray_sumArray(JNIEnv *env,
jobject obj, jintArray arr) {
// 我們不能直接這樣操作。
/* This program is illegal! */
/*int i, sum = 0;
for (i = 0; i < 10; i++) {
sum += arr[i];
}*/
//////////////////////////////////範例程式碼1///////////////////////////
// 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;
///////////////////////////////////範例程式碼2///////////////////////////
jint *carr;
jint j, sum1 = 0;
carr = (*env)->GetIntArrayElements(env, arr, NULL);
if (carr == NULL) {
return 0; /* exception occurred */
}
for (j = 0; j < 10; j++) {
sum1 += carr[j];
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);
return sum1;
JNIEXPORT jint JNICALL Java_org_winplus_basetype_IntArray_sumArray(JNIEnv *env,
jobject obj, jintArray arr) {
// 我們不能直接這樣操作。
/* This program is illegal! */
/*int i, sum = 0;
for (i = 0; i < 10; i++) {
sum += arr[i];
}*/
//////////////////////////////////範例程式碼1///////////////////////////
// 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;
///////////////////////////////////範例程式碼2///////////////////////////
jint *carr;
jint j, sum1 = 0;
carr = (*env)->GetIntArrayElements(env, arr, NULL);
if (carr == NULL) {
return 0; /* exception occurred */
}
for (j = 0; j < 10; j++) {
sum1 += carr[j];
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);
return sum1;
在範例程式碼1中,使用GetIntArrayRegion拷貝數組內容到buf中,這裡沒有做越界異常檢測,因為知道數組有10個,參數3為待拷貝數組的起始位置,參數4為拷貝元素的個數。
JNI支援SetIntArrayRegion允許重新設定數組一個地區的值,其他基本類型(boolean,short, 和float)也有對應的支援。
範例程式碼2示範通過Get/Release<Type>ArrayElemetns返回Java數組的一個拷貝(實現優良的VM,會返回指向Java數組的一個直接的指標,並標記該記憶體地區,不允許被GC)。
作者:tangcheng_ok