1. 程式人生 > >JNI學習(四)、本地方法建立java物件,以及對字串的操作

JNI學習(四)、本地方法建立java物件,以及對字串的操作

一、本地方法建立Java物件

JNIEnv提供了下面的幾個方法來建立一個Java物件:

jobject NewObject(jclass clazz, jmethodID methodID, ...) 
jobject NewObjectV(jclass clazz, jmethodID methodID,va_list args) 
jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue *args) {

對於這三個方法的區別,在JNI學習(三)中已經說得汗清楚了,它們只是傳入形參的方式不一樣,作用是一模一樣的。

第一個引數jclass class  代表的你要建立哪個類的物件

第二個引數jmethodID methodID 代表你要使用哪個構造方法ID來建立這個物件。

只要有jclass和jmethodID ,我們就可以在本地方法建立這個Java類的物件。

指的一提的是:取得構造方法的jmethodID,與取得普通方法的jmethodID有一點不一樣。

取得普通方法的jmethodID :

jmethodID id_date=env->GetMethodID(clazz,"show","(DD)V");
第二個引數指定的是要取得的普通方法的名稱,第三個為方法簽名,表示有兩個double的引數,返回值為Void

但是構造方法的取得和普通方法不一樣,因為構造方法的名稱和類名是一樣的,所以第二個引數統一指定為"<init>"。並且構造方法沒有返回值,所以統一指定返回值簽名為“V“

比如,我們要建立一個java.util.Date物件,那麼可以使用下面的方式:

jclass clazz=env->FindClass("java/util/Date"); //取得java.util.Date類的jclass物件
	jmethodID id_date=env->GetMethodID(clazz,"Date","()V");  //取得某一個構造方法的jmethodID
	jobject date=env->NewObject(clazz,id_date);//呼叫NewObject方法建立java.util.Date物件

//獲得Date物件後,就可以使用Date物件裡面的方法了。
jmethodID id_getTime=env->GetMethodID(clazz,"getTime","()J");
    jlong time=env->CallLongMethod(date,id_getTime);
    cout<<time<<endl;


還有一種建立Java物件的方式,但是不常用:使用JNIEnv提供的函式AllocObject,該函式可以根據傳入的jclass建立一個Java物件,但是他的狀態時非初始化的,在使用這個物件之前絕對要用CallNonvirtualVoidMethod來呼叫該jclass的建構函式,這樣可以延遲建構函式的呼叫。

比如:

jclass clazz=env->FindClass("java/util/Date");
        jmethodID   methodID_str=env->GetMethodID(clazz,"<init>","()V");
	//預先建立一個沒有初始化的字元
	jobject date=env->AllocObject(clazz);
	//呼叫構造方法
	env->CallNonvirtualVoidMethod(date,clazz,methodID_str);
	//使用它
	jmethodID id_getTime=env->GetMethodID(clazz,"getTime","()J");
        jlong time=env->CallLongMethod(date,id_getTime);
        cout<<time<<endl;

二、對java字串的操作

在Java中,使用的字串String物件是Unicoode(UTF-16)碼,即每個字元不論是中文還是英文還是符號,一個字元總是佔用兩個位元組。

但是在C/C++中一個字元是一個位元組,但是C/C++中的寬字元是兩個位元組的。Java通過JNI介面可以將Java的字串轉換到C/C++的寬字串(wchar_t*),或是傳回一個UTF-8的字串(char*)到C/C++,反過來,C/C++可以通過一個寬字串,或是一個UTF-8編碼的字串建立一個Java端的String物件。

可以看下面的一個例子:

在Java端有一個字串 String string="abcde";,現在在本地方法中取得它並且輸出:

JNIEXPORT void JNICALL Java_com_tao_test_Test_show
  (JNIEnv * env, jobject obj)
{
	jfieldID id_string=env->GetFieldID(env->GetObjectClass(obj),"string","Ljava/lang/String;");//取得該字串的jfieldID
	jstring string=(jstring)(env->GetObjectField(obj,id_string));    //取得該字串,強轉為jstring型別。
        printf("%s",string);
}
然後再Java端呼叫該本地方法,最後輸出結果為:

可以看到,從java端取得的String屬性或者是從方法的String物件返回值,對應在JNI中都是jstring型別,它並不是C/C++中的字串。jstring型別與C/C++中的字串型別是不一樣的, 所以,我們需要對取得的 jstring型別的字串進行一系列的轉換,才能使用。

JNIEnv提供了一系列的方法來操作字串。

1、

const jchar *GetStringChars(jstring str, jboolean *isCopy)

將一個jstring物件,轉換為(UTF-16)編碼的寬字串(jchar*)。

const char *GetStringUTFChars(jstring str, jboolean *isCopy)

將一個jstring物件,轉換為(UTF-8)編碼的字串(char*)

這兩個函式的引數含義:

第一個引數傳入一個指向Java 中String物件的jstring變數

第二個引數傳入的是一個jboolean的指標。可以使NULL,JNI_TRUE或者JNI_FLASE。如果為JNI_TRUE則表示開闢記憶體,然後把Java中的String拷貝到這個記憶體中,然後返回指向這個記憶體地址的指標。如果為JNI_FALSE,則直接返回指向Java中String的記憶體指標。這個時候千萬不要改變這個記憶體中的內容,這將破壞String在Java中始終是常量的規則。如果是NULL,則表示不關心是否拷貝字串,它就不會給jboolean*指向的記憶體賦值。

使用這兩個函式取得的字元,在不適用的時候,要分別對應的使用下面兩個函式來釋放記憶體。

RealeaseStringChars(jstring jstr,const jchar* str)

RealeaseStringUTFChars(jstring jstr,const char* str)

第一個引數指定一個jstring變數,即要釋放的本地字串的資源

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

2、

為了增加直接傳回指向Java字串的可能性(而不是拷貝),JDK1.2出來了新的函式GetStringCritical/RealeaseStringCritical

const jchar * GetStringCritical(jstring string, jboolean *isCopy)
void ReleaseStringCritical(jstring string, const jchar *cstring)

在GetStringCritical/RealeaseStringCritical之間是一個關鍵區,在這個關鍵區中絕對不能呼叫JNI的其他函式,也不能有會造成當前執行緒中斷或是讓當前執行緒等待的任何原生代碼。否則將造成關鍵區程式碼執行期間垃圾回收器停止運作任何觸發垃圾回收器的執行緒也會暫停,其他的垃圾回收器的執行緒不能前進直到當前的執行緒結束而啟用垃圾回收器。

並且在關鍵區中千萬不要出現中斷操作,或是在JVM中分配任何新物件,否則會造成JVM死鎖。

雖說這個函式會增加直接傳回指向Java字串的指標的可能性,不過還是會根據情況傳回拷貝的字串。

沒有GetStringUTFCritical函式,因為Java字串是UTF-16的編碼,要轉換為UTF-8編碼的字串始終要進行一次拷貝,所以不存在這樣的函式。

3、

GetStringRegion  /  GetStringUTFRegion

這也是Java1.2新增的函式,這個函式的動作,是把Java字串的內容直接拷貝到C/C++的字元陣列中。在呼叫這個函式之前必須有一個C/C++分配出來的字串,然後傳入到這個函式中進行字串的拷貝。

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

 void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf)     //拷貝java字串並且UTF-8編碼傳入buffer

void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) //拷貝java字串並且UTF-16編碼傳入buffer

4、

jstring NewString(const jchar *unicode, jsize len)   //根據傳入的寬字串建立一個Java String物件

jstring NewStringUTF(const char *utf)    ////根據傳入的UTF-8字串建立一個Java String物件

5、

jsize GetStringLength(jstring jstr)   //返回一個java String物件的字串長度

jsize GetStringUTFLength(jstring jstr) //返回一個java String物件 經過UTF-8編碼後的字串長度

三、處理Java的陣列

我們可以使用GetFieldID獲取一個Java陣列變數的ID,然後用GetObjectFiled取得該陣列變數到本地方法,返回值為jobject,然後我們可以強制轉換為j<Type>Array型別。

可以明白Java的陣列,在JNI中都是j<Type>Array的型別。具體的型別如下:如jbooleanArray,jbyteArray等等

typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;

j<Type>Array型別是JNI定義的一個物件型別,它並不是C/C++的陣列,如int[]陣列,double[]陣列等等。所以我們要把j<Type>Array型別轉換為C/C++中的陣列。

JNIEnv定義了一系列的方法來把一個j<Type>Array型別轉換為C/C++陣列,和把C/C++陣列轉換為j<Type>Array

 jsize GetArrayLength(jarray array) 

 jobjectArray NewObjectArray(jsize len, jclass clazz,jobject init)
 jobject GetObjectArrayElement(jobjectArray array, jsize index) 
 void SetObjectArrayElement(jobjectArray array, jsize index,jobject val) 

 jbooleanArray NewBooleanArray(jsize len) 
 jbyteArray NewByteArray(jsize len) 
 jcharArray NewCharArray(jsize len) 
 jshortArray NewShortArray(jsize len) 
 jintArray NewIntArray(jsize len) 
 jlongArray NewLongArray(jsize len) 
 jfloatArray NewFloatArray(jsize len) 
 jdoubleArray NewDoubleArray(jsize len) 

 jboolean * GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy) 
 jbyte * GetByteArrayElements(jbyteArray array, jboolean *isCopy) 
 jchar * GetCharArrayElements(jcharArray array, jboolean *isCopy) 
 jshort * GetShortArrayElements(jshortArray array, jboolean *isCopy) 
 jint * GetIntArrayElements(jintArray array, jboolean *isCopy) 
 jlong * GetLongArrayElements(jlongArray array, jboolean *isCopy) 
 jfloat * GetFloatArrayElements(jfloatArray array, jboolean *isCopy) 
 jdouble * GetDoubleArrayElements(jdoubleArray array, jboolean *isCopy) 

 void ReleaseBooleanArrayElements(jbooleanArray array,jboolean *elems,jint mode) 
 void ReleaseByteArrayElements(jbyteArray array,jbyte *elems,jint mode) 
 void ReleaseCharArrayElements(jcharArray array,jchar *elems,jint mode) 
 void ReleaseShortArrayElements(jshortArray array,jshort *elems,jint mode) 
 void ReleaseIntArrayElements(jintArray array,jint *elems,jint mode) 
 void ReleaseLongArrayElements(jlongArray array,jlong *elems,jint mode) 
 void ReleaseFloatArrayElements(jfloatArray array,jfloat *elems,jint mode) 
 void ReleaseDoubleArrayElements(jdoubleArray array,jdouble *elems,jint mode)

 void * GetPrimitiveArrayCritical(jarray array, jboolean *isCopy) 
 void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode) 

 void GetBooleanArrayRegion(jbooleanArray array,jsize start, jsize len, jboolean *buf) 
 void GetByteArrayRegion(jbyteArray array,jsize start, jsize len, jbyte *buf) 
 void GetCharArrayRegion(jcharArray array,jsize start, jsize len, jchar *buf) 
 void GetShortArrayRegion(jshortArray array,jsize start, jsize len, jshort *buf) 
 void GetIntArrayRegion(jintArray array,jsize start, jsize len, jint *buf) 
 void GetLongArrayRegion(jlongArray array,jsize start, jsize len, jlong *buf) 
 void GetFloatArrayRegion(jfloatArray array,jsize start, jsize len, jfloat *buf) 
 void GetDoubleArrayRegion(jdoubleArray array,jsize start, jsize len, jdouble *buf) 

 void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len,const jboolean *buf) 
 void SetByteArrayRegion(jbyteArray array, jsize start, jsize len,const jbyte *buf) 
 void SetCharArrayRegion(jcharArray array, jsize start, jsize len,const jchar *buf) 
 void SetShortArrayRegion(jshortArray array, jsize start, jsize len,const jshort *buf) 
 void SetIntArrayRegion(jintArray array, jsize start, jsize len,const jint *buf) 
 void SetLongArrayRegion(jlongArray array, jsize start, jsize len,const jlong *buf) 
 void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len,const jfloat *buf) 
 void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len,const jdouble *buf) 

上面是JNIEnv提供的所有運算元組的方法,大致可以分為下面幾類。

1、獲取陣列的長度

 jsize GetArrayLength(jarray array) 
不管是基本型別陣列,還是物件型別陣列

2、物件型別陣列的操作

 jobjectArray NewObjectArray(jsize len, jclass clazz,jobject init)
 jobject GetObjectArrayElement(jobjectArray array, jsize index) 
 void SetObjectArrayElement(jobjectArray array, jsize index,jobject val) 
JNI沒有提供直接把Java的物件型別陣列(Object[ ])直接轉到C++中的jobject[ ]陣列的函式。而是直接通過Get/SetObjectArrayElement這樣的函式來對Java的Object[ ]陣列進行操作

3、基本資料型別陣列的操作

基本資料型別陣列的操作方法比較多,大致可以分為如下幾類:

①、Get<Type>ArrayElements/Realease<Type>ArrayElements

Get<Type>ArrayElements(<Type>Array arr,jboolean* isCopied);

這類函式可以把Java基本型別的陣列轉換到C/C++中的陣列。有兩種處理方式,一是拷貝一份傳回原生代碼,另一個是把指向Java陣列的指標直接傳回到原生代碼,處理完本地化的陣列後,通過Realease<Type>ArrayElements來釋放陣列

Realease<Type>ArrayElements(<Type>Array arr,<Type>* array,jint mode);用這個函式可以選擇將如何處理Java跟C/C++的陣列,是提交,還是撤銷等,記憶體釋放還是不釋放

mode可以取下面的值:

0       對Java的陣列進行更新並釋放C/C++的陣列

JNI_COMMIT    對Java的陣列進行更新但是不釋放C/C++的陣列

JNI_ABORT    對Java的陣列不進行更新,釋放C/C++的陣列

②、GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical

void * GetPrimitiveArrayCritical(jarray array, jboolean *isCopy) {
void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode)

這兩個函式也是JDK1.2 新增的,為了增加直接傳回指向Java陣列的指標而加入的函式,同樣的,也會有用GetStringCritical的死鎖的問題。

3、Get<Type>ArrayRegion/Set<Type>ArrayRegion

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++的陣列中的元素來賦值

看下面的一個對基本資料型別的操作:

Java端程式碼如下,有一個本地方法和陣列

package com.tao.test;

public class Test {
	private int [] arrays=new int[]{1,2,3,4,5};
	public native void show();
	static{
		System.loadLibrary("NativeTest");
	}
	public static void main(String[] args) {
		new Test().show();
	}
}
本地方法:
JNIEXPORT void JNICALL Java_com_tao_test_Test_show
  (JNIEnv * env, jobject obj)
{
	jfieldID id_arrsys=env->GetFieldID(env->GetObjectClass(obj),"arrays","[I");
	jintArray arr=(jintArray)(env->GetObjectField(obj,id_arrsys));
	jint* int_arr=env->GetIntArrayElements(arr,NULL);
	jsize len=env->GetArrayLength(arr);
	for(int i=0;i<len;i++)
	{
		cout<<int_arr[i]<<endl;
	}
	env->ReleaseIntArrayElements(arr,int_arr,JNI_ABORT);
}
最後在Java中呼叫本地方法,輸出了字串。