4.JNI: 操作字串String

BUG弄潮兒發表於2023-04-15

1. 在C/C++原生程式碼中建立Java的物件

1.1 Java物件的建立使用 NewObject 方法

  • 使用函式 NewObject 可以用來建立Java物件
  • GetMethod 能夠取得構造方法的 jmethodID,如果傳入的要取的方法名稱設定為 "<init>" 就能夠取得構造方法
  • 因為構造方法沒有返回值,所以構造方法的方法返回值型別的簽名始終為void

案例

jclass class_date = env->FindClass("java/util/Date");
jmethodID mid_date = env->GetMethodId(class_date, "<init>", "()V");
jobject now = env->NewObject(class_date, mid_date);


jmethodID mid_date_getTime = env->GetMethod(class_date , "getTime", "()j");
jlong time = CallLongMethod(now, mid_date_getTime);

cout << time << endl;

1.2 Java物件的建立 AllocObject

  • 使用函式 AllocObject 可以根據傳入的 jclass 建立一個 Java 物件,但是他的狀態是非初始化的,在使用這個物件之前一定要呼叫 CallNonvirtualVoidMethod 來呼叫該 jclass 的建構函式,這樣就可以延遲建構函式的呼叫。這一個部分用的很少,在這裡只做簡單的說明。
jclass clazz_str = env->FindClass("java/lang.String");
jmethodID methodID_str = env->GetMethodID(clazz_str, "<init>", "([C)V");

//預先建立一個沒有初始化的字串
jobject str = env->AllocObject(clazz_str);
//建立一個4個元素的字串陣列,然後以 'B' 'U' 'G' '弄' '潮' '兒' 賦值
jcharArray arg = env->NewCharArray(6);
env->SetCharArrayRegion(arg, 0, 6, L"BUG弄潮兒");
//呼叫建構函式
env->CallNonvirtualVoidMethod(str, clazz_str, methodID_str, arg);


jclass clazz_this = env->GetObjectClass( obj );
//這裡假設這個物件的類中有定義 static String STATIC_STR;
jfieldID jfield_str = env->GetStaticField( clazz_this, "STATIC_STR", "Ljava/lang/String;");
env->SetStaticObjectField(clazz_str, jfield_str, str);

2. Java字串 & C/C++的字串

在C/C++原生程式碼中訪問Java的String字串物件

在C/C++原生程式碼中建立Java的String字串物件

  • 在Java中,使用的字串String物件是 Unicode ( UTF-16 ) 編碼,即每個字元不論是中文、英文還是符號,一個字元總是佔兩個位元組
  • Java 透過 JNI 介面可以將 Java的字串轉換到 C/C++ 中的寬字串(wchar_t*),或者是傳回一個UTF-8的字串(char*)到 C/C++。反過來,C/C++ 可以透過一個寬字串,或者是一個 UTF-8 編碼的字串來建立一個Java端的 String 物件

2.1 GetStringChars & GetStringUTFChars

GetStringChars
GetStringUTFChars
  • 這兩個函式用來取得某個jstring物件相關的Java字串。分別可以取得UTF-16編碼的寬字串(jchar*)與UTF8編碼的字串(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 變數,即是要釋放的本地字串的來源;

第二個引數就是要釋放的本地字串;

2.2 GetStringCritical

  • 為了增加直接傳入指向Java字串的指標的可能性(而不是複製),JDK 1.2 增加了函式 GetStringCritical / ReleaseStringCritical
const jchar* GetStringCritical( jstring str, jboolean* copied);
void ReleaseStringCritical( jstring jstr, const jchar* str);
  • 在GetStringCritical /ReleaseStringCritical 之間是一個關鍵區,在這個關鍵區之間絕對不能呼叫 JNI 的其他函式和會造成當前執行緒中斷或是會讓當前執行緒等待的任何原生程式碼。否則將造成關鍵區程式碼執行期間垃圾回收器停止運作,任何觸發垃圾回收器的執行緒也會暫停。其它的觸發垃圾回收器的執行緒不能前進直到當前執行緒結束而啟用垃圾回收器
  • 在關鍵區中千萬不要出現中斷操作,或是在JVM中分配任何新物件;否則會造成JVM死鎖
  • 雖說這個函式會增加直接傳回指向Java字串的指標的可能性,不過還是會根據情況傳回複製過的字串
  • 不支援GetStringUTFCritical,沒有這樣的函式。由於Java字串用的是UTF16編碼,要轉換成UTF8編碼的字串始終需要進行一次複製,所以沒有這樣的函式。

2.3 GetStringRegin & GetStringUTFRegion

  • Java 1.2 增加了 GetStringRegin / GetStringUTFRegion 函式。這個函式的動作,是把Java字串的內容直接複製到 C/C++ 的字元陣列中。在呼叫這個函式之前必須有一個 C/C++ 分配出來的字串,然後傳入到這個函式中進行字串的複製
  • 由於 C/C++ 中分配記憶體開銷相對小,而且Java中的String內容複製的開銷可以忽略,更好的一點是此函式不分配記憶體,不會丟擲OutOfMemoryError 異常
//複製Java字串並以UTF-8編碼傳入buffer
GetStringUTFRegion( jstring srt, jsize start, jsize len, char* buffer);

//複製Java字串並以UTF-16編碼傳入buffer
GetStringRegion( jstring srt, jsize start, jsize len, char* buffer);

2.4 其他字串函式

jstring NewString( const jchar* str, jsize len);

jstring NewStringUTF(const char* str);

jsize GetStringLength(jstring str);

jsize GetStringUTFLength(jstring str);

相關文章