基礎(chǔ)數(shù)據(jù)類型

開發(fā)者使用JNI時(shí)最常問到的是JAVA和C/C++之間如何傳遞數(shù)據(jù)驳庭,以及數(shù)據(jù)類型之間如何互相映射活尊。本章我們從整數(shù)等基本類型和數(shù)組撼玄、字符串等普通的對象類型開始講述炬搭。至于如何傳遞任意對象蜈漓,我們將在下一章中進(jìn)行講述。

3.1 一個(gè)簡單的本地方法

JAVA端源代碼如下:

class Prompt {
 // native method that prints a prompt and reads a line
 private native String getLine(String prompt);

 public static void main(String args[]) {
     Prompt p = new Prompt();
     String input = p.getLine("Type a line: ");
     System.out.println("User typed: " + input);
 }
 static {
     System.loadLibrary("Prompt");
 }
 }

3.1.1 本地方法的C函數(shù)原型

Prompt.getLine方法可以用下面這個(gè)C函數(shù)來實(shí)現(xiàn):

JNIEXPORT jstring JNICALL 
 Java_Prompt_getLine(JNIEnv *env, jobject this, jstring prompt);

其中宫盔,JNIEXPORT和JNICALL這兩個(gè)宏(被定義在jni.h)確保這個(gè)函數(shù)在本地庫外可見融虽,并且C編譯器會進(jìn)行正確的調(diào)用轉(zhuǎn)換。C函數(shù)的名字構(gòu)成有些講究灼芭,在11.3中會有一個(gè)詳細(xì)的解釋有额。

3.1.2 本地方法參數(shù)

第一個(gè)參數(shù)JNIEnv接口指針,指向一個(gè)個(gè)函數(shù)表姿鸿,函數(shù)表中的每一個(gè)入口指向一個(gè)JNI函數(shù)谆吴。本地方法經(jīng)常通過這些函數(shù)來訪問JVM中的數(shù)據(jù)結(jié)構(gòu)。圖3.1演示了JNIEnv這個(gè)指針:


圖3.1 JNIEnv接口指針
第二個(gè)參數(shù)根據(jù)本地方法是一個(gè)靜態(tài)方法還是實(shí)例方法而有所不同苛预。本地方法是一個(gè)靜態(tài)方法時(shí)句狼,第二個(gè)參數(shù)代表本地方法所在的類;本地方法是一個(gè)實(shí)例方法時(shí)热某,第二個(gè)參數(shù)代表本地方法所在的對象腻菇。我們的例子當(dāng)中胳螟,Java_Prompt_getLine是一個(gè)實(shí)例方法,因此jobject參數(shù)指向方法所在的對象筹吐。

3.1.3 類型映射
本地方法聲明中的參數(shù)類型在本地語言中都有對應(yīng)的類型糖耸。JNI定義了一個(gè)C/C++類型的集合,集合中每一個(gè)類型對應(yīng)于JAVA中的每一個(gè)類型丘薛。
JAVA中有兩種類型:基本數(shù)據(jù)類型(int,float,char等)和引用類型(類嘉竟,對象,數(shù)組等)洋侨。
JNI對基本類型和引用類型的處理是不同的舍扰。基本類型的映射是一對一的希坚。例如JAVA中的int類型直接對應(yīng)C/C++中的jint(定義在jni.h中的一個(gè)有符號 32位整數(shù))边苹。12.1.1包含了JNI中所有基本類型的定義。
JNI把JAVA中的對象當(dāng)作一個(gè)C指針傳遞到本地方法中裁僧,這個(gè)指針指向JVM中的內(nèi)部數(shù)據(jù)結(jié)構(gòu)个束,而內(nèi)部數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中的存儲方式是不可見的。本地代碼必須通過在JNIEnv中選擇適當(dāng)?shù)腏NI函數(shù)來操作JVM中的對象聊疲。例如茬底,對于java.lang.String對應(yīng)的JNI類型是jstring,但本地代碼只能通過GetStringUTFChars這樣的JNI函數(shù)來訪問字符串的內(nèi)容售睹。
所有的JNI引用都是jobject類型桩警,對了使用方便和類型安全,JNI定義了一個(gè)引用類型集合昌妹,集合當(dāng)中的所有類型都是jobject的子類型捶枢。這些子類型和JAVA中常用的引用類型相對應(yīng)。例如飞崖,jstring表示字符串烂叔,jobjectArray表示對象數(shù)組。

3.2 訪問字符串

Java_Prompt_getLine接收一個(gè)jstring類型的參數(shù)prompt固歪,jstring類型指向JVM內(nèi)部的一個(gè)字符串蒜鸡,和常規(guī)的C字符串類型char*不同。你不能把jstring當(dāng)作一個(gè)普通的C字符串牢裳。

3.2.1 轉(zhuǎn)換為本地字符串

本地代碼中逢防,必須使用合適的JNI函數(shù)把jstring轉(zhuǎn)化為C/C++字符串。JNI支持字符串在Unicode和UTF-8兩種編碼之間轉(zhuǎn)換蒲讯。Unicode字符串代表了16-bit的字符集合忘朝。UTF-8字符串使用一種向上兼容7-bit ASCII字符串的編碼協(xié)議。UTF-8字符串很像NULL結(jié)尾的C字符串判帮,在包含非ASCII字符的時(shí)候依然如此局嘁。所有的7-bitASCII字符的值都在1~127之間溉箕,這些值在UTF-8編碼中保持原樣。一個(gè)字節(jié)如果最高位被設(shè)置了悦昵,意味著這是一個(gè)多字節(jié)字符(16-bitUnicode值)肴茄。
函數(shù)Java_Prompt_getLine通過調(diào)用JNI函數(shù)GetStringUTFChars來讀取字符串的內(nèi)容。GetStringUTFChars可以把一個(gè)jstring指針(指向JVM內(nèi)部的Unicode字符序列)轉(zhuǎn)化成一個(gè)UTF-8格式的C字符串但指。如何你確信原始字符串?dāng)?shù)據(jù)只包含7-bit ASCII字符寡痰,你可以把轉(zhuǎn)化后的字符串傳遞給常規(guī)的C庫函數(shù)使用,如printf枚赡。我們會在8.2中討論如何處理非ASCII字符串氓癌。

JNIEXPORT jstring JNICALL 
 Java_Prompt_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("%s", buf);
 return

不要忘記檢查GetStringUTFChars谓谦。因?yàn)镴VM需要為新誕生的UTF-8字符串分配內(nèi)存贫橙,這個(gè)操作有可能因?yàn)閮?nèi)存太少而失敗。失敗時(shí)反粥,GetStringUTFChars會返回NULL卢肃,并拋出一個(gè)OutOfMemoryError異常(對異常的處理在第6章)。這些JNI拋出的異常與JAVA中的異常是不同的才顿。一個(gè)由JNI拋出的未決的異常不會改變程序執(zhí)行流莫湘,因此,我們需要一個(gè)顯示的return語句來跳過C函數(shù)中的剩余語句郑气。Java_Prompt_getLine函數(shù)返回后幅垮,異常會在Prompt.main(Prompt.getLine這個(gè)發(fā)生異常的函數(shù)的調(diào)用者)中拋出,
3.2.2 釋放本地字符串資源
從GetStringUTFChars中獲取的UTF-8字符串在本地代碼中使用完畢后尾组,要使用ReleaseStringUTFChars告訴JVM這個(gè)UTF-8字符串不會被使用了忙芒,因?yàn)檫@個(gè)UTF-8字符串占用的內(nèi)存會被回收。
3.2.3 構(gòu)造新的字符串
你可以通過JNI函數(shù)NewStringUTF在本地方法中創(chuàng)建一個(gè)新的java.lang.String字符串對象讳侨。這個(gè)新創(chuàng)建的字符串對象擁有一個(gè)與給定的UTF-8編碼的C類型字符串內(nèi)容相同的Unicode編碼字符串呵萨。
如果一個(gè)VM不能為構(gòu)造java.lang.String分配足夠的內(nèi)存,NewStringUTF會拋出一個(gè)OutOfMemoryError異常跨跨,并返回一個(gè)NULL潮峦。在這個(gè)例子中,我們不必檢查它的返回值勇婴,因?yàn)楸镜胤椒〞⒓捶祷爻类凇H绻鸑ewStringUTF失敗,OutOfMemoryError這個(gè)異常會被在Prompt.main(本地方法的調(diào)用者)中拋出耕渴。如果NeweStringUTF成功拘悦,它會返回一個(gè)JNI引用,這個(gè)引用指向新創(chuàng)建的java.lang.String對象萨螺。這個(gè)對象被Prompt.getLine返回然后被賦值給Prompt.main中的本地input窄做。
3.2.4 其它JNI字符串處理函數(shù)
JNI支持許多操作字符串的函數(shù)愧驱,這里做個(gè)大致介紹。
GetStringChars和ReleaseStringChars獲取以Unicode格式編碼的字符串椭盏。當(dāng)操作系統(tǒng)支持Unicode編碼的字符串時(shí)组砚,這些方法很有用。
UTF-8字符串以’\0’結(jié)尾掏颊,而Unicode字符串不是糟红。如果jstring指向一個(gè)Unicode編碼的字符串,為了得到這個(gè)字符串的長度乌叶,可以調(diào)用GetStringLength盆偿。如果一個(gè)jstring指向一個(gè)UTF-8編碼的字符串,為了得到這個(gè)字符串的字節(jié)長度准浴,可以調(diào)用標(biāo)準(zhǔn)C函數(shù)strlen事扭。或者直接對jstring調(diào)用JNI函數(shù)GetStringUTFLength乐横,而不用管jstring指向的字符串的編碼格式求橄。
GetStringChars和GetStringUTFChars函數(shù)中的第三個(gè)參數(shù)需要更進(jìn)一步的解釋:
const jchar *
GetStringChars(JNIEnv *env, jstring str, jboolean *isCopy);
當(dāng)從JNI函數(shù)GetStringChars中返回得到字符串B時(shí),如果B是原始字符串java.lang.String的拷貝葡公,則isCopy被賦值為JNI_TRUE罐农。如果B和原始字符串指向的是JVM中的同一份數(shù)據(jù),則isCopy被賦值為JNI_FALSE催什。當(dāng)isCopy值為JNI_FALSE時(shí)涵亏,本地代碼決不能修改字符串的內(nèi)容,否則JVM中的原始字符串也會被修改蒲凶,這會打破JAVA語言中字符串不可變的規(guī)則气筋。
通常,因?yàn)槟悴槐仃P(guān)心JVM是否會返回原始字符串的拷貝豹爹,你只需要為isCopy傳遞NULL作為參數(shù)裆悄。
JVM是否會通過拷貝原始Unicode字符串來生成UTF-8字符串是不可以預(yù)測的,程序員最好假設(shè)它會進(jìn)行拷貝臂聋,而這個(gè)操作是花費(fèi)時(shí)間和內(nèi)存的光稼。一個(gè)典型的JVM會在heap上為對象分配內(nèi)存。一旦一個(gè)JAVA字符串對象的指針被傳遞給本地代碼孩等,GC就不會再碰這個(gè)字符串艾君。換言之,這種情況下肄方,JVM必須pin這個(gè)對象冰垄。可是权她,大量地pin一個(gè)對象是會產(chǎn)生內(nèi)存碎片的虹茶,因?yàn)槭判剑摂M機(jī)會隨意性地來選擇是復(fù)制還是直接傳遞指針。
當(dāng)你不再使用一個(gè)從GetStringChars得到的字符串時(shí)蝴罪,不管JVM內(nèi)部是采用復(fù)制還是直接傳遞指針的方式董济,都不要忘記調(diào)用ReleaseStringChars。根據(jù)方法GetStringChars是復(fù)制還是直接返回指針要门,ReleaseStringChars會釋放復(fù)制對象時(shí)所占的內(nèi)存虏肾,或者unpin這個(gè)對象。

3.2.5 JDK1.2中關(guān)于字符串的新JNI函數(shù)

為了提高JVM返回字符串直接指針的可能性欢搜,JDK1.2中引入了一對新函數(shù)封豪,Get/ReleaseStringCritical。表面上炒瘟,它們和Get/ReleaseStringChars函數(shù)差不多吹埠,但實(shí)際上這兩個(gè)函數(shù)在使用有很大的限制。
使用這兩個(gè)函數(shù)時(shí)唧领,你必須兩個(gè)函數(shù)中間的代碼是運(yùn)行在"critical region"(臨界區(qū))的藻雌,即,這兩個(gè)函數(shù)中間的本地代碼不能調(diào)用任何會讓線程阻塞或等待JVM中的其它線程的本地函數(shù)或JNI函數(shù)斩个。
有了這些限制, JVM就可以在本地方法持有一個(gè)從GetStringCritical得到的字符串的直接指針時(shí)禁止GC驯杜。當(dāng)GC被禁止時(shí)受啥,任何線程如果觸發(fā)GC的話,都會被阻塞鸽心。而Get/ReleaseStringCritical這兩個(gè)函數(shù)中間的任何本地代碼都不可以執(zhí)行會導(dǎo)致阻塞的調(diào)用或者為新對象在JVM中分配內(nèi)存滚局。否則,JVM有可能死鎖顽频,想象一下這樣的場景中:
1藤肢、 只有當(dāng)前線程觸發(fā)的GC完成阻塞并釋放GC時(shí),由其它線程觸發(fā)的GC才可能由阻塞中釋放出來繼續(xù)運(yùn)行糯景。
2嘁圈、 在這個(gè)過程中,當(dāng)前線程會一直阻塞蟀淮。因?yàn)槿魏巫枞哉{(diào)用都需要獲取一個(gè)正被其它線程持有的鎖最住,而其它線程正等待GC。
Get/ReleaseStringCritical的交迭調(diào)用是安全的怠惶,這種情況下涨缚,它們的使用必須有嚴(yán)格的順序限制。而且策治,我們一定要記住檢查是否因?yàn)閮?nèi)存溢出而導(dǎo)致它的返回值是NULL脓魏。因?yàn)镴VM在執(zhí)行GetStringCritical這個(gè)函數(shù)時(shí)兰吟,仍有發(fā)生數(shù)據(jù)復(fù)制的可能性,尤其是當(dāng)JVM內(nèi)部存儲的數(shù)組不連續(xù)時(shí)茂翔,為了返回一個(gè)指向連續(xù)內(nèi)存空間的指針揽祥,JVM必須復(fù)制所有數(shù)據(jù)。
總之檩电,為了避免死鎖拄丰,在Get/ReleaseStringCritical之間不要調(diào)用任何JNI函數(shù)。Get/ReleaseStringCritical和 Get/ReleasePrimitiveArrayCritical這兩個(gè)函數(shù)是可以的俐末。
下面代碼演示了這對函數(shù)的正確用法:

jchar *s1, *s2;
 s1 = (*env)->GetStringCritical(env, jstr1);
 if (s1 == NULL) {
 ... /* error handling */
 }
 s2 = (*env)->GetStringCritical(env, jstr2);
 if (s2 == NULL) {
 (*env)->ReleaseStringCritical(env, jstr1, s1);
 ... /* error handling */
 }
 ...     /* use s1 and s2 */
 (*env)->ReleaseStringCritical(env, jstr1, s1);
 (*env)->ReleaseStringCritical(env, jstr2, s2);

JNI不支持Get/ReleaseStringUTFCritical料按,因?yàn)檫@樣的函數(shù)在進(jìn)行編碼轉(zhuǎn)換時(shí)很可能會促使JVM對數(shù)據(jù)進(jìn)行復(fù)制,因?yàn)镴VM內(nèi)部表示字符串一般都是使用Unicode的卓箫。
JDK1.2還一對新增的函數(shù):GetStringRegion和GetStringUTFRegion载矿。這對函數(shù)把字符串復(fù)制到一個(gè)預(yù)先分配的緩沖區(qū)內(nèi)秃励。Prompt.getLine這個(gè)本地方法可以用GetStringUTFRegion重新實(shí)現(xiàn)如下:

JNIEXPORT jstring JNICALL 
 Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
 {
 /* assume the prompt string and user input has less than 128
    characters */
 char outbuf[128], inbuf[128];
 int len = (*env)->GetStringLength(env, prompt);
 (*env)->GetStringUTFRegion(env, prompt, 0, len, outbuf);
 printf("%s", outbuf);
 scanf("%s", inbuf);
 return (*env)->NewStringUTF(env, inbuf);
 }

GetStringUTFRegion這個(gè)函數(shù)會做越界檢查氓润,如果必要的話绽媒,會拋出異常StringIndexOutOfBoundsException勃救。這個(gè)方法與GetStringUTFChars比較相似谤辜,不同的是趋惨,GetStringUTFRegion不做任何內(nèi)存分配邑跪,不會拋出內(nèi)存溢出異常粉铐。

3.2.6 JNI字符串操作函數(shù)總結(jié)

對于小字符串來說藐吮,Get/SetStringRegion和Get/SetString-UTFRegion這兩對函數(shù)是最佳選擇溺拱,因?yàn)榫彌_區(qū)可以被編譯器提前分配,而且永遠(yuǎn)不會產(chǎn)生內(nèi)存溢出的異常谣辞。當(dāng)你需要處理一個(gè)字符串的一部分時(shí)迫摔,使用這對函數(shù)也是不錯(cuò)的,因?yàn)樗鼈兲峁┝艘粋€(gè)開始索引和子字符串的長度值泥从。另外句占,復(fù)制少量字符串的消耗是非常小的。
在使用GetStringCritical時(shí)躯嫉,必須非常小心纱烘。你必須確保在持有一個(gè)由GetStringCritical獲取到的指針時(shí),本地代碼不會在JVM內(nèi)部分配新對象和敬,或者做任何其它可能導(dǎo)致系統(tǒng)死鎖的阻塞性調(diào)用凹炸。
下面的例子演示了使用GetStringCritical時(shí)需要注意的一些地方:

/* This is not safe! */
 const char *c_str = (*env)->GetStringCritical(env, j_str, 0);
 if (c_str == NULL) {
 ... /* error handling */
 }
 fprintf(fd, "%s\n", c_str);
(*env)->ReleaseStringCritical(env, j_str, c_str);

上面代碼的問題在于,GC被當(dāng)前線程禁止的情況下昼弟,向一個(gè)文件寫數(shù)據(jù)不一定安全啤它。例如,另外一個(gè)線程T正在等待從文件fd中讀取數(shù)據(jù)。假設(shè)操作系統(tǒng)的規(guī)則是fprintf會等待線程T完成所有對文件fd的數(shù)據(jù)讀取操作变骡,這種情況下就可能會產(chǎn)生死鎖:線程T從文件fd中讀取數(shù)據(jù)是需要緩沖區(qū)的离赫,如果當(dāng)前沒有足夠內(nèi)存,線程T就會請求GC來回收一部分塌碌,GC一旦運(yùn)行渊胸,就只能等到當(dāng)前線程運(yùn)行ReleaseStringCritical時(shí)才可以。而ReleaseStringCritical只有在fprintf調(diào)用返回時(shí)才會被調(diào)用台妆。而fprintf這個(gè)調(diào)用翎猛,會一直等待線程T完成文件讀取操作。

3.3 訪問數(shù)組

JNI在處理基本類型數(shù)組和對象數(shù)組上面是不同的接剩。對象數(shù)組里面是一些指向?qū)ο髮?shí)例或者其它數(shù)組的引用切厘。
本地代碼中訪問JVM中的數(shù)組和訪問JVM中的字符串有些相似“萌保看一個(gè)簡單的例子疫稿。下面的程序調(diào)用了一個(gè)本地方法sumArray,這個(gè)方法對一個(gè)int數(shù)組里面的元素進(jìn)行累加:

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");
 }
 }

3.3.1 在本地代碼中訪問數(shù)組

數(shù)組的引用類型是一般是jarray或者或者jarray的子類型jintArray鹃两。就像jstring不是一個(gè)C字符串類型一樣遗座,jarray也不是一個(gè)C數(shù)組類型。所以俊扳,不要直接訪問jarray途蒋。你必須使用合適的JNI函數(shù)來訪問基本數(shù)組元素:

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;
 }

3.3.2 訪問基本類型數(shù)組

上一個(gè)例子中,使用GetIntArrayRegion函數(shù)來把一個(gè)int數(shù)組中的所有元素復(fù)制到一個(gè)C緩沖區(qū)中拣度,然后我們在本地代碼中通過C緩沖區(qū)來訪問這些元素碎绎。
JNI支持一個(gè)與GetIntArrayRegion相對應(yīng)的函數(shù)SetIntArrayRegion。這個(gè)函數(shù)允許本地代碼修改所有的基本類型數(shù)組中的元素抗果。
JNI支持一系列的Get/Release<Type>ArrayElement函數(shù),這些函數(shù)允許本地代碼獲取一個(gè)指向基本類型數(shù)組的元素的指針奸晴。由于GC可能不支持pin操作冤馏,JVM可能會先對原始數(shù)據(jù)進(jìn)行復(fù)制,然后返回指向這個(gè)緩沖區(qū)的指針寄啼。我們可以重寫上面的本地方法實(shí)現(xiàn):

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;
 }

GetArrayLength這個(gè)函數(shù)返回?cái)?shù)組中元素的個(gè)數(shù)逮光,這個(gè)值在數(shù)組被首次分配時(shí)確定下來。
JDK1.2引入了一對函數(shù):Get/ReleasePrimitiveArrayCritical墩划。通過這對函數(shù)涕刚,可以在本地代碼訪問基本類型數(shù)組元素的時(shí)候禁止GC的運(yùn)行。但程序員使用這對函數(shù)時(shí)乙帮,必須和使用Get/ReleaseStringCritical時(shí)一樣的小心杜漠。在這對函數(shù)調(diào)用的中間,同樣不能調(diào)用任何JNI函數(shù),或者做其它可能會導(dǎo)致程序死鎖的阻塞性操作驾茴。

3.3.3 操作基本類型數(shù)組的JNI函數(shù)的總結(jié)

如果你想在一個(gè)預(yù)先分配的C緩沖區(qū)和內(nèi)存之間交換數(shù)據(jù)盼樟,應(yīng)該使用Get/Set</Type>ArrayRegion系列函數(shù)。這些函數(shù)會進(jìn)行越界檢查锈至,在需要的時(shí)候會有可能拋出ArrayIndexOutOfBoundsException異常晨缴。
對于少量的、固定大小的數(shù)組峡捡,Get/Set<Type>ArrayRegion是最好的選擇击碗,因?yàn)镃緩沖區(qū)可以在Stack(棧)上被很快地分配,而且復(fù)制少量數(shù)組元素的代價(jià)是很小的们拙。這對函數(shù)的另外一個(gè)優(yōu)點(diǎn)就是稍途,允許你通過傳入一個(gè)索引和長度來實(shí)現(xiàn)對子字符串的操作。
如果你沒有一個(gè)預(yù)先分配的C緩沖區(qū)睛竣,并且原始數(shù)組長度未定晰房,而本地代碼又不想在獲取數(shù)組元素的指針時(shí)阻塞的話,使用Get/ReleasePrimitiveArrayCritical函數(shù)對射沟。就像Get/ReleaseStringCritical函數(shù)對一樣殊者,這對函數(shù)很小心地使用,以避免死鎖验夯。
Get/Release<type>ArrayElements系列函數(shù)永遠(yuǎn)是安全的猖吴。JVM會選擇性地返回一個(gè)指針,這個(gè)指針可能指向原始數(shù)據(jù)也可能指向原始數(shù)據(jù)復(fù)制挥转。

3.3.5 訪問對象數(shù)組

JNI提供了一個(gè)函數(shù)對來訪問對象數(shù)組海蔽。GetObjectArrayElement返回?cái)?shù)組中指定位置的元素,而SetObjectArrayElement修改數(shù)組中指定位置的元素绑谣。與基本類型的數(shù)組不同的是党窜,你不能一次得到所有的對象元素或者一次復(fù)制多個(gè)對象元素。字符串和數(shù)組都是引用類型借宵,你要使用Get/SetObjectArrayElement來訪問字符串?dāng)?shù)組或者數(shù)組的數(shù)組幌衣。
下面的例子調(diào)用了一個(gè)本地方法來創(chuàng)建一個(gè)二維的int數(shù)組,然后打印這個(gè)數(shù)組的內(nèi)容:

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");
 }
 }

靜態(tài)本地方法initInt2DArray創(chuàng)建了一個(gè)給定大小的二維數(shù)組壤玫。執(zhí)行分配和初始化數(shù)組任務(wù)的本地方法可以是下面這樣子的:

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;
 }

函數(shù)newInt2DArray首先調(diào)用JNI函數(shù)FindClass來獲得一個(gè)int型二維數(shù)組類的引用豁护,傳遞給FindClass的參數(shù)“[I”是JNI class descriptor(JNI類型描述符),它對應(yīng)著JVM中的int[]類型欲间。如果類加載失敗的話楚里,F(xiàn)indClass會返回NULL,然后拋出一個(gè)異常猎贴。
接下來班缎,NewObjectArray會分配一個(gè)數(shù)組蝴光,這個(gè)數(shù)組里面的元素類型用intArrCls類引用來標(biāo)識。函數(shù)NewObjectArray只能分配第一維吝梅,JVM沒有與多維數(shù)組相對應(yīng)的數(shù)據(jù)結(jié)構(gòu)虱疏。一個(gè)二維數(shù)組實(shí)際上就是一個(gè)簡單的數(shù)組的數(shù)組。
創(chuàng)建第二維數(shù)據(jù)的方式非常直接苏携,NewInt-Array為每個(gè)數(shù)組元素分配空間做瞪,然后SetIntArrayRegion把tmp[]緩沖區(qū)中的內(nèi)容復(fù)制到新分配的一維數(shù)組中去。
在循環(huán)最后調(diào)用DeleteLocalRef右冻,確保JVM釋放掉iarr這個(gè)JNI引用装蓬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纱扭,隨后出現(xiàn)的幾起案子牍帚,更是在濱河造成了極大的恐慌,老刑警劉巖乳蛾,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暗赶,死亡現(xiàn)場離奇詭異,居然都是意外死亡肃叶,警方通過查閱死者的電腦和手機(jī)蹂随,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來因惭,“玉大人岳锁,你說我怎么就攤上這事”哪В” “怎么了激率?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長勿决。 經(jīng)常有香客問我乒躺,道長,這世上最難降的妖魔是什么低缩? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任聪蘸,我火速辦了婚禮,結(jié)果婚禮上表制,老公的妹妹穿的比我還像新娘。我一直安慰自己控乾,他們只是感情好么介,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜕衡,像睡著了一般壤短。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天久脯,我揣著相機(jī)與錄音纳胧,去河邊找鬼。 笑死帘撰,一個(gè)胖子當(dāng)著我的面吹牛跑慕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播摧找,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼核行,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蹬耘?” 一聲冷哼從身側(cè)響起芝雪,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎综苔,沒想到半個(gè)月后惩系,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡如筛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年堡牡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妙黍。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悴侵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拭嫁,到底是詐尸還是另有隱情可免,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布做粤,位于F島的核電站浇借,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏怕品。R本人自食惡果不足惜妇垢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肉康。 院中可真熱鬧闯估,春花似錦、人聲如沸吼和。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炫乓。三九已至刚夺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侠姑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工妥畏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人船老。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓咖熟,卻偏偏與公主長得像柳畔,于是被迫代替她去往敵國和親馍管。 傳聞我的和親對象是個(gè)殘疾皇子薪韩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容