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