JNIEnv
定義: 相當於一個jni上下文物件。
作用: 通過JNIEnv的指標能夠對Java端的程式碼進行操作:
a.建立Java物件.
jstring str = (env).NewStringUTF("終端研發部");
jclass jclazz = (env).GetObjectClass(obj);b.呼叫Java物件的方法。
jclz = (*env)->FindClass(env, "java/lang/String"); jdouble doub = (*env).GetStaticDoubleField()複製程式碼
c.獲取及設定Java物件的屬性。
jintArray arrayResult = NULL; jclass jclazz = (*env).GetObjectClass(obj); jint * elements =(*env).GetIntArrayElements(array,NULL);複製程式碼
Get/Set[Static]
Method JNI中通常用JType指代Java環境中的類。
typedef _jobject *jobject;
typedef _jclass *jclass;
typedef _jthrowable *jthrowable;
typedef _jstring *jstring;
typedef _jarray *jarray;
typedef _jbooleanArray *jbooleanArray;
typedef _jbyteArray *jbyteArray;
typedef _jcharArray *jcharArray;
typedef _jshortArray *jshortArray;
typedef _jintArray *jintArray;
typedef _jlongArray *jlongArray;
typedef _jfloatArray *jfloatArray;
typedef _jdoubleArray *jdoubleArray;
typedef _jobjectArray *jobjectArray;複製程式碼
JType都繼承自JObject
[cpp] view plain copy
class _jobject {};
class _jclass : public _jobject {};
class _jthrowable : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jobjectArray : public _jarray {};複製程式碼
jobject的理解
JNIEXPORT void JNICALL Java_com_jue_testnative_TestNative1_hello(JNIEnv *, jobject);
這裡是jobject指代的在Java中呼叫native方法的java類例項複製程式碼
獲取jclass的方法
a. jclass FindClass(const char *name)
b. jclass GetObjectClass(jobject obj)複製程式碼
FindClass注意事項:
注意: 會在ClassPath下面尋找類。需要傳入完整類名,注意包與包之間用’/'。
jclass class_str = env->FindClass("java/lang/String");複製程式碼
jfiledID/jmethodID的獲取
在natvie方法中獲取/設定欄位的值,或者方法呼叫,需要先獲取相應的field/method的ID
jfieldID GetFieldID(jclass clazz, const char name, const char sig)
jmethodID GetMethodID(jclass clazz, const char name, const char sig)
注意sig用來處理函式過載引起的不確定。
然後通過ID獲取
jobject GetObjectField(jobject obj, jfieldID fieldID)
jobject CallObjectMethod(jobject obj, jmethodID methodID, ...) 複製程式碼
獲取方法的簽名
javap -s 命令工具可以檢視一個類的方法的簽名
javap -s xxx.class
Sin簽名含義細節
型別 | 相應的簽名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int I | |
long | L |
float | F |
double | D |
void | V |
String | Ljava/lang/String |
Array | [Ljava/lang/Object |
Method | (para s1,para s2) 返回值簽名 |
jni如何呼叫Java裡面的方法的
NIEnv提供了一下函函式(能夠實現子類物件呼叫父類方法的功能)
- Call
Method, - CallStatic
Method, CallNonvirtual
Method。 CallVoidMethod(jobject obj, jmethodID methodID, ...) void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args) void CallVoidMethodA(jobject obj, jmethodID methodID, jvalue* args)複製程式碼
呼叫例項方法的三種方法
Call
Method(jobject obj, jmethodID id, ...); boolean funcation(int i, double d, char c) { } env->CallBooleanMethod( obj, id_function , 100L, 3.44, L'3');複製程式碼
Call
MethodV(jobject obj, jmethodID id,va_list lst); Call
MethodA(jobject obj, jmethodID id, jvalue* v);
```
jvalue是一個聯合體[cpp] view plain copy
typedef union jvalue {jboolean z; jbyte b; jchar c; jshort s; jint i; jlong j; jfloat f; jdouble d; jobject l; 複製程式碼
} jvalue;
jvalue *args = new jvalue[3];
args[0].i = 100L;
args[1].d = 3.44;
args[2].c = L'3';
env->CallBooleanMethodA(obj, id_funcation, args);
delete [] args;
```複製程式碼
Java物件的建立
- 方式-NewObject
使用NewObject來建立Java物件。
jobject NewObject(jclass clazz, jmethodID methodID, ...)複製程式碼
需要先活的相應構造器的name,方法名設定為
jmethodID GetMethodID(jclass clazz, const char *name, const char *sig)複製程式碼
例子:
jclass class_date = env->FindClass("java/util/Date");
jmethodID method_id = env->GetMethodID(class_date,"<init>","()V");
jobject now = env->NewObject(class_date, method_id);複製程式碼
- b.方式-AllocObject
```
jclass cls = (env).GetObjectClass(obj);
jobject oobj = (env).AllocObject(cls);
#### Java字串和C/C++字串之間的轉換
- a.在java中字串String物件是Unicode(UTF-16)碼,每個字元無論中文還是英文都佔用兩個位元組。
- b.可以通過JNI介面把Java中的字串轉換成C/C++中的寬字串,Java也可以傳一個UTF-8的字串(char*) 到C/C++中。
- c.反過來C++可以通過把一個寬字串,或者一個UTF-8的字串來建立一個Java端的物件。
#### memset的注意事項
- C庫函式 void *memset(void *str, int c, size_t n) 複製字元c(unsigned char型別)引數str指向的字串的前n個字元。
- memset是以位元組為單位,初始化記憶體塊。
當初始化一個位元組單位的陣列時,可以用memset把每個陣列單元初始化成任何你想要的值
`複製程式碼
char data[10];
memset(data, 1, sizeof(data)); // right
memset(data, 0, sizeof(data));
char str[50];
strcpy(str,"This is string.h library function");
puts(str);
memset(str,'$',7);
puts(str);
```複製程式碼
- 在初始化其他基礎型別時,則需要注意,比如
int data[10];
memset(data, 0, sizeof(data)); // right
memset(data, -1, sizeof(data)); // right
memset(data, 1, sizeof(data)); // wrong, data[x] would be 0x0101 instead of 1複製程式碼
獲取字串
- a.取得與某個jstring物件相關的Java字串
方法 | 作用
---|---
GetStringChars | 取得UTF-16編碼的寬字串(char*)
GetStringUTFChars | 取得UTF-8編碼的字串(char*)複製程式碼
注意在不使用到的使用,要注意使用ReleaseStringChars,或者ReleaseStringUTFChars釋放拷貝的記憶體,或Java物件的引用。
還可以使用下面的方法,這個方法可以增加返回jvm中java物件的可能性,但是還是有可能返回相應java串的拷貝。
這個方法沒有相應的GetStringUTFCritical,由於Java字串使用的是UTF-16,要轉換成UTF-8編碼的串本來就需要一次拷貝動作。
陣列分為2種
a.基本型別的陣列。
b.物件型別(Object[])的陣列。
有一個通用於兩種不同陣列的的函式:得到資料的長度函式:GetArrayLength
處理基本型別陣列
跟處理字串相似
Get
需要用
Release
void ReleaseIntArrayElements(jintArray array,
jint *elems,
jint mode) {
functions->ReleaseIntArrayElements(this,array,elems,mode);
}複製程式碼
mode的型別:
0:對Java陣列進行更新,並釋放C/C++的陣列。
JNI_COMMIT: 對Java陣列進行更新但不釋放C/C++陣列。
JNI_ABORT:對Java陣列不進行更新,並釋放C/C++陣列。
相似GetStringUTFCritical的函式:為了直接傳回指向Java陣列的指標而加入的函式。
void * GetPrimitiveArrayCritical(jarray array, jboolean *isCopy) {
return functions->GetPrimitiveArrayCritical(this,array,isCopy);
}
void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode) {
functions->ReleasePrimitiveArrayCritical(this,array,carray,mode);
}複製程式碼
Get
void GetIntArrayRegion(jintArray array,
jsize start, jsize len, jint *buf) {
functions->GetIntArrayRegion(this,array,start,len,buf);
}複製程式碼
Set
void SetIntArrayRegion(jintArray array, jsize start, jsize len,
const jint *buf) {
functions->SetIntArrayRegion(this,array,start,len,buf);
}複製程式碼
建立一個基本型別的Java陣列
處理Object型別陣列
JNI中沒有把Java物件型別的陣列轉換成C++中的jobject[]的函式。而是直接通過Get/SetObjectArrayElement來對Java的Object陣列進行操作。
jobject GetObjectArrayElement(jobjectArray array, jsize index) {
return functions->GetObjectArrayElement(this,array,index);
}
void SetObjectArrayElement(jobjectArray array, jsize index,
jobject val) {
functions->SetObjectArrayElement(this,array,index,val);
}複製程式碼
使用上述方式不需要釋放資源。
可以根據陣列長度和初始值來建立某個類的陣列
jobjectArray NewObjectArray(jsize len, jclass clazz,
jobject init) {
return functions->NewObjectArray(this,len,clazz,init);
}複製程式碼
java物件在JNI中的引用
在jvm中建立的物件被傳到本地的C/C++程式碼的時候,會產生引用。根據jvm的垃圾回收機制,只要引用存在,就不會觸發該引用指向物件的被回收。
這些引用分為三種
區域性引用Local Reference:是最常見的引用,區域性引用只在native函式中有效。區域性引用的存在會阻止其指向物件的回收。
全域性引用Global Reference:
可以跨越多個執行緒
在多個navtive方法中有效、
全域性引用存在期間會阻止其引用java物件在jvm的回收。
全域性引用的建立不是JNI自動建立的。
全域性引用的建立要顯示的呼叫NewGlobalRef函式,而釋放需要呼叫ReleaseGlobalRef
弱全域性引用:
與全域性引用相似,建立和釋放都需要由程式設計人員來進行。
多個執行緒,多個native方法中有效。
區別:這個引用不會阻止jvm回收其指向的引用。
NewWeakGlobalRef與ReleaseWeakGlobalRef來建立和釋放引用。
IsSameObject
IsSameObject在弱全域性引用中有一個特別的功能。
把NULL傳給要判斷的object,來判斷弱全域性引用指向的java物件是否被回收。
快取jfieldID,jmethodID.
a.通過方法名+簽名來查詢jfieldID,jmethod開銷是非常大的。
b.快取方式:
1.在使用的時候快取。(Caching at the Point of Use)
使用static型別的區域性變數來儲存已經查詢過的ID,這樣就不會在每次呼叫的時候查詢,而是隻查詢一次。
2.在java類初始化的時候快取。(Caching at Class's inititalizer)
這種載入方式在jvm類的載入和重新載入都會重新呼叫該native方法重新計算ID
public class TestNative
{
static {
initNativeIDs();
}
static natvie void initNativeIDs();
int propInt = 0;
}複製程式碼
JNIEXPORT void JNICALL Java_TestNative_initNativeIDs(JNIEnv *env, jobject object)
{
......
g_propInt_id = GetFieldID(clazz, "propInt", "I" );
}複製程式碼
相信自己,沒有做不到的,只有想不到的
如果你覺得此文對您有所幫助,歡迎入群 QQ交流群 :644196190
微信公眾號:終端研發部