|
在c/c++本地代碼中訪問java的String對(duì)象 .在java中,使用的字符串String對(duì)象是Unicode碼,即每個(gè)字符不論是中文還是英文或是符號(hào),一個(gè)字符總是占用兩個(gè)字節(jié)。 在c/c++本地代碼中創(chuàng)建java的String對(duì)象 .java通過JNI接口可以將java的字符串轉(zhuǎn)換到c/c++中的寬字符串(wchar_t *),或是傳回一個(gè)UTF-8的字符串(char *)到c/c++。反過來,c/c++可以通過一個(gè)寬字符串,或是一個(gè)UTF-8編碼的字符串來創(chuàng)建一個(gè)java端的String對(duì)象。 GetStringChars/GetStringUTFChars .這兩個(gè)函數(shù)用來取得與某個(gè)jstring對(duì)象相關(guān)的java字符串。分別可以取得UTF-16編碼的寬字符串(jchar*)跟UTF-8編碼的字符串(char*)。 Const jchar* GetStringChars(jstring str, jboolean* copied) Const char* GetStringUTFChars(jstring str, jboolean* copied) 第一個(gè)參數(shù)傳入一個(gè)指向java中的String對(duì)象的jstring變量 第二個(gè)參數(shù)傳入的是一個(gè)jboolean的指針。 這兩個(gè)函數(shù)分別都會(huì)有兩個(gè)不同的動(dòng)作: 第一個(gè)參數(shù): 1、 開新內(nèi)存,然后把java中的String拷貝到這個(gè)內(nèi)存中,然后返回這個(gè)內(nèi)存地址的指針。 2、 直接返回指向java中string的內(nèi)存的指針,這個(gè)時(shí)候千萬不要改變這個(gè)內(nèi)存的內(nèi)容,這將破壞String在java中始終是常量這個(gè)原則。 第二個(gè)參數(shù):是用來標(biāo)示是否對(duì)java的string對(duì)象進(jìn)行了拷貝的。 如果傳入的這個(gè)jboolean指針不是null,則他會(huì)給該指針指向的內(nèi)存?zhèn)魅?/span>JNI_TRUE或JNI_FALSE標(biāo)示是否進(jìn)行了拷貝。 傳入null標(biāo)示不關(guān)心是否拷貝字符串,它就不會(huì)給jboolean*指向的內(nèi)存賦值。 使用這兩個(gè)函數(shù)取得的字符串,在不使用的時(shí)候,要使用ReleaseStringChars/ReleaseStringUTFChars來釋放拷貝的內(nèi)存,或是釋放對(duì)java的String對(duì)象的引用。 ReleaseStringChars(jstring jstr, const jchar* str); ReleaseStringUTFChars(jstring jstr, const char* str); 第一個(gè)參數(shù)指定一個(gè)jstring變量,即是要釋放的本地字符串的來源。 第二個(gè)參數(shù)就是要釋放的本地字符串 GetStringCritical:是為了增加直接傳回指向java字符串的指針的可能性(而不是拷貝),jdk1.2出來了新的函數(shù):GetStringCritical/ReleaseStringCritical。 Const jchar* GetStringCritical(jstring str, jboolean* copied) Void realeaseStringCritical(jstring jstr, const jchar* str); 在GetStringCritical/RealeaseStringCritical之間是一個(gè)關(guān)鍵區(qū)。在這關(guān)鍵區(qū)之中絕對(duì)不能呼叫JNI的其他函數(shù)和會(huì)造成當(dāng)前線程中斷或是會(huì)讓當(dāng)前線程等待的任何本地代碼,否則將造成關(guān)鍵區(qū)代碼執(zhí)行區(qū)間垃圾回收器停止運(yùn)作,任何觸發(fā)垃圾回收器的線程也會(huì)暫停。其他的觸發(fā)垃圾回收器的線程不能前進(jìn)直到當(dāng)前線程結(jié)束而激活垃圾回收器。 在關(guān)鍵區(qū)中千萬不雅出現(xiàn)中斷操作,或是在jvm中分配任何新對(duì)象。否則會(huì)造成jvm死鎖。 雖說這個(gè)函數(shù)會(huì)增加直接傳回指向java字符串的指針的可能性,不過還是會(huì)根據(jù)情況傳回拷貝過的字符串。 不支持GetStringUTFCritical,沒有這樣的函數(shù)。由于java字符串用的是UTF16,要轉(zhuǎn)成UTF8編碼的字符串始終需要進(jìn)行一個(gè)拷貝。所以沒有這樣的函數(shù)。 GetStringRegion/GetStringUTFRegion .java1.2出來的函數(shù),這個(gè)函數(shù)的動(dòng)作,是把java字符串的內(nèi)容直接拷貝到c/c++的字符數(shù)組中。在呼叫這個(gè)函數(shù)之前必須有一個(gè)c/c++分配出來的字符串,然后傳入到這個(gè)函數(shù)中進(jìn)行字符串的拷貝。 由于c/c++中分配內(nèi)存開銷相對(duì)小,而且java中的String內(nèi)容拷貝的開銷可以忽略,更好的一點(diǎn)是此函數(shù)不分配內(nèi)存,不會(huì)拋出OutOfMemoeryError異常。 //拷貝java字符串并以UTF-8編碼傳入buffer. GetStringUTFRegion(String str, jsize start, jsize len, char* buffer); //拷貝java字符串并以UTF-16編碼傳入buffer GetStringRegion(jstring str, jsize start, jsize len, jchar* buffer);
其他的字符串函數(shù): .jstring NewString(const jchar* str, jszie len); 寬字符串(c/c++中的普通的字符串) .jstring NewStringUTF(const char* str); .jsize GetStringLength(jstring str); .jsize GetStringUTFLength(jstring str); 例如:紅線圈住的是對(duì)應(yīng)的方法應(yīng)用。Message是定義在java類中,在此省略了代碼
處理數(shù)組:數(shù)組分為兩種(基本類型的數(shù)組、對(duì)象類型的數(shù)組) 一個(gè)能通用于兩種不同類型數(shù)組的函數(shù):GetArrayLength(jarray array) 處理--基本類型的數(shù)組: .處理基本類型的數(shù)組跟處理字符串類似,也有很相似的函數(shù)。 .Get<TYPE>ArrayElements(<TYPE>Array arr, jboolean* isCopied);這類的函數(shù)可以把java基本類型的數(shù)組轉(zhuǎn)換到c/c++中的數(shù)組。有兩種處理方式,一是拷貝一份傳回本地代碼,另一個(gè)是把指向java數(shù)組的指針傳回到本地代碼。處理完本地化的數(shù)組后,通過Release<TYPE>ArrayElements來釋放數(shù)組。 .Release<TYPE>ArrayElements(<TYPE>Array arr, <TYPE>* array, jint mode);用這個(gè)函數(shù)可以選擇將如何處理java跟c++的數(shù)組。是提交,還是撤銷等,內(nèi)存釋放還是不釋放。mode可以去下面的值:0(對(duì)java的數(shù)組進(jìn)行更新并釋放c/c++的數(shù)組) JNI_COMMIT(對(duì)java的數(shù)組進(jìn)行更新但不釋放c/c++的數(shù)組) JNI_ABORT (對(duì)java的數(shù)組不進(jìn)行更新,釋放c/c++的數(shù)組) .GetPrimitiveArrayCritical(jarray arr, jboolean* isCopied); ReleasePrimitiveArrayCritical(jarray arr, void* array, jint mode);它們也是jdk1.2出來的,為了增加直接傳回指向java數(shù)組的指針而加入的函數(shù)。同樣的,也會(huì)有同GetStringCritical的死鎖的問題。 .Get<TYPE>ArrayRegion(<TYPE>Array arr, jsize start, jsize len,<TYPE>* buffer);在c/c++預(yù)先開辟一段內(nèi)存,然后把java基本類型的數(shù)組拷貝到這段內(nèi)存中。跟GetStringRegion原理類似。 .Set<TYPE>ArrayRegion(<TYPE>Array arr, jsize start, jsize len, const<TYPE>* buffer);把java基本類型的數(shù)組中的指定范圍的元素用c/c++的數(shù)組中的元素來賦值。 .<TYPE>Array New<TYPE>Array(jsize size);指定一個(gè)長度然后返回相應(yīng)的java基本類型的數(shù)組。
處理—對(duì)象類型的數(shù)組【Object[]】: JNI沒有提供直接把java的對(duì)象類型數(shù)組(Object[])直接轉(zhuǎn)到c/c++中的jobject[]數(shù)組的函數(shù);而是直接通過GetObjectArrayElement (JNIEnv *env, jobjectArray array, jsize index)/ SetObjectArrayElement (JNIEnv *env, jobjectArray array, jsize index, jobject val)這樣的函數(shù)來對(duì)java的Object[]數(shù)組進(jìn)行操作。 .使用上述的函數(shù)也不用釋放任何資源。 .NewObjectArray(jsize len, jclass clazz, jobject init)可以通過指定長度跟初始值來創(chuàng)建某個(gè)類的數(shù)組。 int[] arrays = {1,2,3,4,5,6,7,8,9,0}; public native void callCppFunction(); public static void main(String[] args) { MainTest obj = new MainTest(); obj.callCppFunction();//這里會(huì)去訪問數(shù)組。 for(int each: obj.arrays) { System.out.println(each); } }
全局引用/局部引用/弱全局引用 .從java虛擬機(jī)創(chuàng)建的對(duì)象傳到本地c/c++代碼時(shí)會(huì)產(chǎn)生引用。根據(jù)java的垃圾回收機(jī)制,只要引用存在就不會(huì)觸發(fā)該引用指向的java對(duì)象的垃圾回收。 .這些引用在JNI中分為三種: 局部引用(Local Reference);最常見的引用類型。基本上通過JNI返回來的引用都是局部引用。 例如使用NewObject就會(huì)返回創(chuàng)建出來的實(shí)例的局部引用。局部引用只在該Native函數(shù)中有效。所有在該函數(shù)中產(chǎn)生的局部引用,都會(huì)在函數(shù)返回的時(shí)候自動(dòng)釋放。也可以使用deleteLocalRef函數(shù)手動(dòng)釋放該引用。 注意:既然局部引用能夠在函數(shù)返回時(shí)自動(dòng)釋放,為什么還需要deleteRef()函數(shù)呢? 實(shí)際上局部引用存在,就會(huì)防止其指向的對(duì)象被垃圾回收。尤其是當(dāng)一個(gè)局部引用指向一個(gè)龐大的對(duì)象,或是在一個(gè)循環(huán)中生成了局部引用,最好的做法就是在使用完該對(duì)象后,或在該循環(huán)尾部把這個(gè)引用釋放掉,以確保在垃圾回收器被觸發(fā)的時(shí)候被回收。 在局部引用的有效期中,可以傳遞到別的本地函數(shù)中,要強(qiáng)調(diào)的是它的有效期仍然只在一次的java本地函數(shù)調(diào)用中,所以千萬不能用c++全局變量保存它或是把它定義為c++靜態(tài)局部變量。 全局引用(Global Reference); 全局引用可以跨越當(dāng)前線程,在多個(gè)native函數(shù)中有效,不過需要編程人員手動(dòng)來釋放該引用。全局引用存在期間會(huì)防止在java的垃圾回收器中回收。 與局部引用不同的是,全局引用過的創(chuàng)建不是由JNI自動(dòng)創(chuàng)建的,全局引用是需要調(diào)用NewGlobalRef函數(shù),而釋放它需要使用ReleaseGlobalRef函數(shù)。 弱全局引用(Weak Global Reference); 其是jdk1.2出來的功能,與全局引用相似,創(chuàng)建跟刪除都需要編程人員手動(dòng)來進(jìn)行。這種引用與全局引用一樣可以在多個(gè)本地代碼有效,也跨越多線程有效。不一樣的是,這種夾棍不會(huì)阻止垃圾回收器回收這個(gè)引用所指向的對(duì)象。 使用NewWeakGlobalRef跟ReleaseGlobalRef來產(chǎn)生和接觸引用。 關(guān)于的一些函數(shù):jobject NewGlobalRef(jobject obj); Jobject NewLocalRef(jobject obj); Jobject NewWeakGlobalRef(jobject obj); Void DeleteGlobalRef(jobject obj); Void DeleteLocalRef(jobject obj); Void DeleteWeakGlobalRef(jobject obj); Jboolean IsSameObject(jobject obj1, jobject obj2);這個(gè)函數(shù)對(duì)于弱全局引用還有一個(gè)特別的功能:把null傳入要比較的對(duì)象中,就能夠判斷弱全局引用所指向的java對(duì)象是否被回收?!?/span>java native interface》 緩存JfieldID/jmethodID .取得jfieldID和jmethodID的時(shí)候會(huì)通過該屬性/方法名稱上簽名來查詢相應(yīng)的jfieldID/jmethodID.這種查詢相對(duì)來說開銷較大。其實(shí),可以將這些FieldID/MethodID緩存起來,這樣只需查詢一次,以后就是使用緩存起來的FieldID/MethodID了。 介紹兩種緩存方式的實(shí)現(xiàn): 1、 在用的時(shí)候緩存: .在native code中使用static局部變量來保存已經(jīng)查詢過的id,這樣就不會(huì)在每次的函數(shù)調(diào)用是查詢,而只要第一次查詢成功后就保存起來了。 .不過在這種情況下不得不考慮多線程同時(shí)呼叫此函數(shù)時(shí)可能會(huì)造成同時(shí)查詢的情況。不過這種情況下沒必要擔(dān)心,所以是無害即是線程安全的。因?yàn)榉祷赝粋€(gè)ID值 Static fieldID fieldID_string = null; Jclass clazz = env->GetObjectClass(obj); If(fieldID_string == null) {fieldID_string = envo->GetFieldID(clazz, “string”, “Ljava/lang/String;”); … 2、 在java類初始化是緩存 .更好的方法就是在任何native函數(shù)調(diào)用錢把id全部存起來。 .可以在第一次加載這個(gè)類的時(shí)候首先調(diào)用本地代碼初始化所有的jfieldID/jmethodID,這樣的話就可以省去多次得確定ID是否存在的語句,當(dāng)然,這些jfieldID/jmethodID是定義在c/c++的全局。 .當(dāng)java類卸載或重新加載的時(shí)候也會(huì)重新呼叫給本地代碼來重新計(jì)算緩存的ID集。 (篇二完,后期篇是關(guān)于JNI異常處理/多線程/c/c++如何啟動(dòng)jvm(上面都沒有涉及,在后期博客會(huì)更新,因?yàn)榭从⑽牡臅g和理解又慢點(diǎn)。) |
|
|