前言
通過(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 操作步驟
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即可漱贱。異常檢查槐雾。
調(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)前方法品追。調(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利虫。調(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)試篇