JNI&NDK開發(fā)最佳實(shí)踐(六):JNI實(shí)現(xiàn)本地方法時(shí)的數(shù)據(jù)類型轉(zhuǎn)換

前言

通過(guò)《JNI數(shù)據(jù)類型及與Java數(shù)據(jù)類型的映射關(guān)系》一文,我們知道了在C中實(shí)現(xiàn)java聲明的本地方法時(shí)么介,方法入?yún)⒂衅湟灰粚?duì)應(yīng)關(guān)系谭跨。但在實(shí)現(xiàn)方法的方法體中,這些數(shù)據(jù)類型是否可以拿來(lái)直接使用呢乎澄?
舉個(gè)例子突硝,在java文件中聲明本地方法如下

public static native void test(short s, int i, long l, float f, double d, char c,
boolean z, byte b, String str, Object obj, MyClass p, int[] arr);

其在c中的對(duì)應(yīng)實(shí)現(xiàn)如下

JNIEXPORT void JNICALL Java_com_study_jnilearn_HelloWorld_test
(JNIEnv *env, jclass cls, jshort s, jint i, jlong l, jfloat f,
jdouble d, jchar c, jboolean z, jbyte b, jstring j_str, jobject jobj1, jobject job2, jintArray j_int_arr)
{
...
}

-基本數(shù)據(jù)類型如jint,jdouble是否可以直接操作?
-可以置济。
-引用數(shù)據(jù)類型如jstring,jxxArray是否可以直接操作解恰?
-不可以锋八。JNI接口提供了許多函數(shù)來(lái)進(jìn)行這樣的轉(zhuǎn)換。

1. C方法中操作jstring

test函數(shù)接收一個(gè)jstring類型的參數(shù)str护盈,但jstring類型是指向JVM內(nèi)部的一個(gè)字符串挟纱,和C風(fēng)格的字符串類型char* 不同,所以在JNI中不能通把jstring當(dāng)作普通C字符串一樣來(lái)使用腐宋,必須使用合適的JNI函數(shù)來(lái)訪問JVM內(nèi)部的字符串?dāng)?shù)據(jù)結(jié)構(gòu)紊服。
因?yàn)镴ava默認(rèn)使用Unicode編碼,而C/C++默認(rèn)使用UTF編碼脏款,所以在本地代碼中操作字符串的時(shí)候围苫,必須使用合適的JNI函數(shù)把jstring轉(zhuǎn)換成C風(fēng)格的字符串。JNI支持字符串在Unicode和UTF-8兩種編碼之間轉(zhuǎn)換撤师。
GetStringUTFChars可以把一個(gè)jstring指針(指向JVM內(nèi)部的Unicode字符序列)轉(zhuǎn)換成一個(gè)UTF-8格式的C字符串剂府。在上例中test函數(shù)中我們通過(guò)GetStringUTFChars正確取得了JVM內(nèi)部的字符串內(nèi)容。

1.1 操作步驟

  1. GetStringUTFChars(env, j_str, &isCopy) 訪問字符串剃盾。
    isCopy:取值JNI_TRUE和JNI_FALSE腺占,如果值為JNI_TRUE,表示返回JVM內(nèi)部源字符串的一份拷貝痒谴,并為新產(chǎn)生的字符串分配內(nèi)存空間衰伯。如果值為JNI_FALSE,表示返回JVM內(nèi)部源字符串的指針积蔚,意味著可以通過(guò)指針修改源字符串的內(nèi)容意鲸,不推薦這么做,因?yàn)檫@樣做就打破了Java字符串不能修改的規(guī)定尽爆。但我們?cè)陂_發(fā)當(dāng)中怎顾,并不關(guān)心這個(gè)值是多少,通常情況下這個(gè)參數(shù)填NULL即可漱贱。

  2. 異常檢查槐雾。
    調(diào)用完GetStringUTFChars之后不要忘記安全檢查, if(c_str == NULL) { return NULL; }因?yàn)镴VM需要為新誕生的字符串分配內(nèi)存空間幅狮,當(dāng)內(nèi)存空間不夠分配的時(shí)候募强,會(huì)導(dǎo)致調(diào)用失敗,失敗后GetStringUTFChars會(huì)返回NULL崇摄,并拋出一個(gè)OutOfMemoryError異常擎值。JNI的異常和Java中的異常處理流程是不一樣的,Java遇到異常如果沒有捕獲逐抑,程序會(huì)立即停止運(yùn)行幅恋。而JNI遇到未決的異常不會(huì)改變程序的運(yùn)行流程,也就是程序會(huì)繼續(xù)往下走泵肄,這樣后面針對(duì)這個(gè)字符串的所有操作都是非常危險(xiǎn)的捆交, 因此,我們需要用return語(yǔ)句跳過(guò)后面的代碼腐巢,并立即結(jié)束當(dāng)前方法品追。

  3. 調(diào)用ReleaseStringUTFChars釋放字符串。
    在調(diào)用GetStringUTFChars函數(shù)從JVM內(nèi)部獲取一個(gè)字符串之后冯丙,JVM內(nèi)部會(huì)分配一塊新的內(nèi)存肉瓦,用于存儲(chǔ)源字符串的拷貝,以便本地代碼訪問和修改胃惜。即然有內(nèi)存分配泞莉,用完之后馬上釋放是一個(gè)編程的好習(xí)慣。通過(guò)調(diào)用ReleaseStringUTFChars函數(shù)通知JVM這塊內(nèi)存已經(jīng)不使用了船殉,你可以清除了鲫趁。注意:這兩個(gè)函數(shù)是配對(duì)使用的,用了GetXXX就必須調(diào)用ReleaseXXX利虫。

  4. 調(diào)用NewStringUTF函數(shù)挨厚,創(chuàng)建字符串返回給java。
    通過(guò)調(diào)用NewStringUTF函數(shù)糠惫,會(huì)構(gòu)建一個(gè)新的java.lang.String字符串對(duì)象疫剃。這個(gè)新創(chuàng)建的字符串會(huì)自動(dòng)轉(zhuǎn)換成Java支持的Unicode編碼。如果JVM不能為構(gòu)造java.lang.String分配足夠的內(nèi)存硼讽,NewStringUTF會(huì)拋出一個(gè)OutOfMemoryError異常巢价,并返回NULL。在這個(gè)例子中我們不必檢查它的返回值固阁,如果NewStringUTF創(chuàng)建java.lang.String失敗壤躲,OutOfMemoryError這個(gè)異常會(huì)被在Sample.main方法中拋出。如果NewStringUTF創(chuàng)建java.lang.String成功您炉,則返回一個(gè)JNI引用柒爵,這個(gè)引用指向新創(chuàng)建的java.lang.String對(duì)象。

1.2 示例代碼


JNIEXPORT jstring JNICALL Java_com_study_jnilearn_Sample_sayHello
  (JNIEnv *env, jclass cls, jstring j_str)
{
    const char *c_str = NULL;
    char buff[128] = {0};
    jboolean isCopy;    // 返回JNI_TRUE表示原字符串的拷貝赚爵,返回JNI_FALSE表示返回原字符串的指針
    c_str = (*env)->GetStringUTFChars(env, j_str, &isCopy);
    printf("isCopy:%d\n",isCopy);
    if(c_str == NULL)
    {
        return NULL;
    }
    printf("C_str: %s \n", c_str);
    sprintf(buff, "hello %s", c_str);
    (*env)->ReleaseStringUTFChars(env, j_str, c_str);
    return (*env)->NewStringUTF(env,buff);
}

1.3 其它字符串處理函數(shù)

1> GetStringChars和ReleaseStringChars:這對(duì)函數(shù)和Get/ReleaseStringUTFChars函數(shù)功能差不多棉胀,用于獲取和釋放以Unicode格式編碼的字符串。后者是用于獲取和釋放UTF-8編碼的字符串冀膝。

2> GetStringLength:由于UTF-8編碼的字符串以'\0'結(jié)尾唁奢,而Unicode字符串不是。如果想獲取一個(gè)指向Unicode編碼的jstring字符串長(zhǎng)度窝剖,在JNI中可通過(guò)這個(gè)函數(shù)獲取麻掸。

3> GetStringUTFLength:獲取UTF-8編碼字符串的長(zhǎng)度,也可以通過(guò)標(biāo)準(zhǔn)C函數(shù)strlen獲取赐纱。

4> GetStringCritical和ReleaseStringCritical:提高JVM返回源字符串直接指針的可能性脊奋。

5> GetStringRegion和GetStringUTFRegion:分別表示獲取Unicode和UTF-8編碼字符串指定范圍內(nèi)的內(nèi)容熬北。這對(duì)函數(shù)會(huì)把源字符串復(fù)制到一個(gè)預(yù)先分配的緩沖區(qū)內(nèi)。

2. C 方法中操作數(shù)組

NI中的數(shù)組分為基本類型數(shù)組和對(duì)象數(shù)組诚隙,它們的處理方式是不一樣的讶隐,基本類型數(shù)組中的所有元素都是JNI的基本數(shù)據(jù)類型,可以直接訪問久又。而對(duì)象數(shù)組中的所有元素是一個(gè)類的實(shí)例或其它數(shù)組的引用巫延,和字符串操作一樣,不能直接訪問Java傳遞給JNI層的數(shù)組地消,必須選擇合適的JNI函數(shù)來(lái)訪問和設(shè)置Java層的數(shù)組對(duì)象炉峰。

2.1 操作基本類型數(shù)組

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_study_jnilearn_IntArray */
 
#ifndef _Included_com_study_jnilearn_IntArray
#define _Included_com_study_jnilearn_IntArray
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_study_jnilearn_IntArray
 * Method:    sumArray
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_com_study_jnilearn_IntArray_sumArray
  (JNIEnv *, jobject, jintArray);
 
#ifdef __cplusplus
}
#endif
#endif
 
// IntArray.c
#include "com_study_jnilearn_IntArray.h"
#include <string.h>
#include <stdlib.h>
 
/*
 * Class:     com_study_jnilearn_IntArray
 * Method:    sumArray
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_com_study_jnilearn_IntArray_sumArray
(JNIEnv *env, jobject obj, jintArray j_array)
{
    jint i, sum = 0;
    jint *c_array;
    jint arr_len;
    //1. 獲取數(shù)組長(zhǎng)度
    arr_len = (*env)->GetArrayLength(env,j_array);
    //2. 根據(jù)數(shù)組長(zhǎng)度和數(shù)組元素的數(shù)據(jù)類型申請(qǐng)存放java數(shù)組元素的緩沖區(qū)
    c_array = (jint*)malloc(sizeof(jint) * arr_len);
    //3. 初始化緩沖區(qū)
    memset(c_array,0,sizeof(jint)*arr_len);
    printf("arr_len = %d ", arr_len);
    //4. 拷貝Java數(shù)組中的所有元素到緩沖區(qū)中
    (*env)->GetIntArrayRegion(env,j_array,0,arr_len,c_array);
    for (i = 0; i < arr_len; i++) {
        sum += c_array[i];  //5. 累加數(shù)組元素的和
    }
    free(c_array);  //6. 釋放存儲(chǔ)數(shù)組元素的緩沖區(qū)
    return sum;
}

2.2 操作引用數(shù)據(jù)類型數(shù)組

JNI提供了兩個(gè)函數(shù)來(lái)訪問對(duì)象數(shù)組,GetObjectArrayElement 返回?cái)?shù)組中指定位置的元素脉执,SetObjectArrayElement 修改數(shù)組中指定位置的元素疼阔。與基本類型不同的是,我們不能一次得到數(shù)據(jù)中的所有對(duì)象元素或者一次復(fù)制多個(gè)對(duì)象元素到緩沖區(qū)适瓦。因?yàn)樽址蛿?shù)組都是引用類型竿开,只能通過(guò) Get/SetObjectArrayElement 這樣的JNI函數(shù)來(lái)訪問字符串?dāng)?shù)組或者數(shù)組中的數(shù)組元素。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_study_jnilearn_ObjectArray */
 
#ifndef _Included_com_study_jnilearn_ObjectArray
#define _Included_com_study_jnilearn_ObjectArray
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_study_jnilearn_ObjectArray
 * Method:    initInt2DArray
 * Signature: (I)[[I
 */
JNIEXPORT jobjectArray JNICALL Java_com_study_jnilearn_ObjectArray_initInt2DArray
  (JNIEnv *, jobject, jint);
 
#ifdef __cplusplus
}
#endif
#endif
 
 
// ObjectArray.c
 
#include "com_study_jnilearn_ObjectArray.h"
/*
 * Class:     com_study_jnilearn_ObjectArray
 * Method:    initInt2DArray
 * Signature: (I)[[I
 */
JNIEXPORT jobjectArray JNICALL Java_com_study_jnilearn_ObjectArray_initInt2DArray
  (JNIEnv *env, jobject obj, jint size)
{
    jobjectArray result;
    jclass clsIntArray;
    jint i,j;
    // 1.獲得一個(gè)int型二維數(shù)組類的引用
    clsIntArray = (*env)->FindClass(env,"[I");
    if (clsIntArray == NULL)
    {
        return NULL;
    }
    // 2.創(chuàng)建一個(gè)數(shù)組對(duì)象(里面每個(gè)元素用clsIntArray表示)
    result = (*env)->NewObjectArray(env,size,clsIntArray,NULL);
    if (result == NULL)
    {
        return NULL;
    }
    
    // 3.為數(shù)組元素賦值
    for (i = 0; i < size; ++i)
    {
        jint buff[256];
        jintArray intArr = (*env)->NewIntArray(env,size);
        if (intArr == NULL)
        {
            return NULL;
        }
        for (j = 0; j < size; j++)
        {
            buff[j] = i + j;
        }
        (*env)->SetIntArrayRegion(env,intArr, 0,size,buff);
        (*env)->SetObjectArrayElement(env,result, i, intArr);
        (*env)->DeleteLocalRef(env,intArr);
    }
    
    return result;
}

3. 總結(jié)

對(duì)于引用類型(jObject)玻熙,將參數(shù)轉(zhuǎn)換或復(fù)制為本地類型否彩,例如將jstring轉(zhuǎn)換為C字符串,將jintArray轉(zhuǎn)換為C的int []等嗦随;對(duì)于原始JNI類型列荔,如jint和jdouble不需要轉(zhuǎn)換,可以直接操作枚尼。

更多JNI&NDK系列文章贴浙,參見:
JNI&NDK開發(fā)最佳實(shí)踐(一):開篇
JNI&NDK開發(fā)最佳實(shí)踐(二):CMake實(shí)現(xiàn)調(diào)用已有C/C++文件中的本地方法
JNI&NDK開發(fā)最佳實(shí)踐(三):CMake實(shí)現(xiàn)調(diào)用已有so庫(kù)中的本地方法
JNI&NDK開發(fā)最佳實(shí)踐(四):JNI數(shù)據(jù)類型及與Java數(shù)據(jù)類型的映射關(guān)系
JNI&NDK開發(fā)最佳實(shí)踐(五):本地方法的靜態(tài)注冊(cè)與動(dòng)態(tài)注冊(cè)
JNI&NDK開發(fā)最佳實(shí)踐(六):JNI實(shí)現(xiàn)本地方法時(shí)的數(shù)據(jù)類型轉(zhuǎn)換
JNI&NDK開發(fā)最佳實(shí)踐(七):JNI之本地方法與java互調(diào)
JNI&NDK開發(fā)最佳實(shí)踐(八):JNI局部引用、全局引用和弱全局引用
JNI&NDK開發(fā)最佳實(shí)踐(九):調(diào)試篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末署恍,一起剝皮案震驚了整個(gè)濱河市崎溃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盯质,老刑警劉巖袁串,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異呼巷,居然都是意外死亡囱修,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門王悍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)破镰,“玉大人,你說(shuō)我怎么就攤上這事∠输觯” “怎么了源譬?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)孕似。 經(jīng)常有香客問我瓶佳,道長(zhǎng),這世上最難降的妖魔是什么鳞青? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮为朋,結(jié)果婚禮上臂拓,老公的妹妹穿的比我還像新娘。我一直安慰自己习寸,他們只是感情好胶惰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著霞溪,像睡著了一般孵滞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸯匹,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天坊饶,我揣著相機(jī)與錄音,去河邊找鬼殴蓬。 笑死匿级,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的染厅。 我是一名探鬼主播痘绎,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肖粮!你這毒婦竟也來(lái)了孤页?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涩馆,失蹤者是張志新(化名)和其女友劉穎行施,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凌净,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悲龟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冰寻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片须教。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出轻腺,到底是詐尸還是另有隱情乐疆,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布贬养,位于F島的核電站挤土,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏误算。R本人自食惡果不足惜仰美,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望儿礼。 院中可真熱鬧咖杂,春花似錦、人聲如沸蚊夫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)知纷。三九已至壤圃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間琅轧,已是汗流浹背伍绳。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹰晨,地道東北人墨叛。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像模蜡,于是被迫代替她去往敵國(guó)和親漠趁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355