1. 程式人生 > >Java中JNI的使用詳解第四篇:C/C++中建立Java物件和String字串物件及對字串的操作方法

Java中JNI的使用詳解第四篇:C/C++中建立Java物件和String字串物件及對字串的操作方法

首先來看一下C/C++中怎麼建立Java物件:在JNIEnv中有兩種方法是用來建立Java物件的:

第一種方法:

jobject  NewObject(jclass clazz  , jmethodID methodID, ....):

引數解釋:

clazz:這個很簡單,就是需要建立的Java物件的Class物件

methodID:這個是傳遞一個方法的ID,想一想Java物件在建立的時候,需要執行什麼方法呢?對,沒錯那就是構造方法

第三個引數:是建構函式需要傳入的引數值(預設的構造方法是不需要傳入這個引數的)

所以我們在建立Java物件的時候之前要做的工作就是要獲取這個物件的class物件,然後再獲取該物件的構造方法,想要獲取方法的id,就需要方法的簽名,因為構造方法沒有返回值,所以我們認為類的預設構造方法的返回值型別的簽名始終是“()V”(因為預設的構造方法是沒有引數的),方法的名稱始終為“<init>”

例子:在C++中構造Java中的Date物件,並且呼叫它的getTime()方法列印當前時間

Java中的程式碼不需要改變,主要是C++程式碼中的改寫:

  1. #include<iostream.h>  
  2. #include "com_jni_demo_JNIDemo.h"
  3. JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)  
  4. {  
  5.     //獲取Java中Date物件的Class物件
  6.     jclass clazz_date = env->FindClass("java/util/Date"
    );  
  7.     //獲取構造方法的id
  8.     jmethodID mid_date = env->GetMethodID(clazz_date,"<init>","()V");  
  9.     //生成Date物件
  10.     jobject now = env->NewObject(clazz_date,mid_date);  
  11.     //獲取Date物件中的getTime方法的id
  12.     jmethodID mid_date_getTime = env->GetMethodID(clazz_date,"getTime","()J");  
  13.         //呼叫getTime方法返回時間
  14.     jlong time = env->CallLongMethod(now,mid_date_getTime);  
  15.     //列印時間,這裡要注意的是不能使用cout輸出了,因為cout並沒有對__int64的輸出進行過載,要輸出的話用printf("%I64d",time);
  16.     printf("%I64d",time);  
  17. }

編譯成.dll檔案,在Eclipse中執行結果如下:


這個輸出的時間沒有進行格式化處理。

第二種方法:

建立一個物件:就是用AllocObject

使用函式AllocObject可以根據傳入的jclass建立一個Java物件,但是他的狀態時非初始化的,在是喲個這個物件之前絕對要用CallNonvirtualVoidMethod來呼叫該jclass的建構函式,這樣就可以延遲建構函式的呼叫,這一部分用的很少,只做簡單的說明:

Java中的程式碼不做任何修改,C++程式碼中修改成如下:

  1. #include<iostream.h>  
  2. #include "com_jni_demo_JNIDemo.h"
  3. JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)  
  4. {  
  5.     //獲取java中的Date物件
  6.     jclass clazz_date = env->FindClass("java/util/Date");  
  7.     jmethodID methodID_str = env->GetMethodID(clazz_date,"<init>","()V");  
  8.     jobject now = env->AllocObject(clazz_date);  
  9.     //呼叫構造方法
  10.     env->CallNonvirtualVoidMethod(now,clazz_date,methodID_str);  
  11.     //獲取Date物件中的getTime方法的id
  12.     jmethodID mid_date_getTime = env->GetMethodID(clazz_date,"getTime","()J");  
  13.         //呼叫getTime方法返回時間
  14.     jlong time = env->CallLongMethod(now,mid_date_getTime);  
  15.     //列印時間,這裡要注意的是不能使用cout輸出了,因為cout並沒有對__int64的輸出進行過載,要輸出的話用printf("%I64d",time);
  16.     printf("%I64d",time);  
  17. }

這種方式是很少用的!

下面來看一下C/C++中如何操作Java中的字串

首先來了解一下Java和C/C++中字串的卻別:(1). 在Java中,使用的字串String物件是Unicode(UTF-16)碼,即每個字元不論是中文還是英文還是符號,一個字元總是佔兩個位元組(2). Java通過JNI介面可以將Java的字串轉換到C/C++中的寬字串(wchar_t *),或是傳回一個UTF-8的字串(char*)到C/C++,反過來,C/C++可以通過一個寬字串,或是一個UTF-8編碼的字串來建立一個Java端的String物件

接下來看一下JNIEnv中的一些C++方法:

1.獲取字串的長度:

jsize GetStringLength(jstring j_msg);

引數:j_msg:是一個jstring物件

2.將jstring物件拷貝到const jchar*指標字串

//這個方法是:拷貝Java字串並以UTF-8編碼傳入jstr

env->GetStringRegion(jstring j_msg , jsize start , jsize len , jchar* jstr);

//這個方法是:拷貝Java字串並以UTF-16編碼傳入jstr

env->GetStringUTFRegion(jstring j_msg , jsize start , jsize len , char* jstr);

在Java1.2出來的函式,這個函式的動作時把Java字串的內容直接拷貝到C/C++的字串陣列中,在呼叫這個函式之前必須有一個C/C++分配出來的字串(具體看下面的例子),然後傳入到這個函式中進行字串的拷貝

由於C/C++中分配記憶體開銷相對小,而且Java中的String內容拷貝的開銷可以忽略,更好的一點是此函式不分配記憶體,不會丟擲OutOfMemoryError異常

引數:j_msg:是一個jstring物件,start是拷貝字串的開始位置,len是拷貝字串的長度,jstr是目標指標字串

3.生成一個jstring物件

jobject NewString(const jchar* jstr , int size);

引數:jstr是字串指標,size是字串長度

這個方法可以認為是將字串指標jstr轉化成字串物件jstring

4.將jstring物件轉化成const jchar*字串指標

(1) const* jchar* GetStringChars(jstring j_msg , jboolean* copied);

返回一個UTF-16編碼的寬字串(jchar*)

引數:

j_msg是字串物件

copied是指傳入的是一個jboolean指標,用來標識是否對Java的String物件進行了拷貝的,如果傳入的這個jboolean指標不是NULL,則它會給該指標所指向的記憶體傳入JNI_TRUE或JNI_FALSE標識是否進行了拷貝,傳入NULL表示不關心是否拷貝字串,它就不會給jboolean* 指向的記憶體賦值

其對應的釋放記憶體指標的方法:

ReleaseStringChars(jstring  j_msg , const jchar* jstr);

引數:j_msg是jstring物件,jstr是字串指標

在這裡還有一個方法就是:

(2) const char* GetStringUTFChars(jstring str , jboolean* copied)

這個方法是可以取得UTF-8編碼的字串(char*)

引數的含義和GetStringChars方法是一樣的

這個方法也有對應的一個釋放記憶體的方法:

ReleaseStringUTFChars(jstring jstr , const char*str);

引數的含義和上面的ReleaseStringChars方法的引數的含義是一樣的

需要注意的是:

這兩個函式分別都會有兩個不同的動作:

(1) 開闢一個新記憶體,然後在Java中的String拷貝到這個記憶體中,然後返回指向這個記憶體地址的指標

(2) 直接返回指向Java中String的記憶體的指標,這個時候千萬不要改變這個記憶體的內容,這個將會破壞String在Java中始終是常量的這個原則

5.將jstring