看王择佑老师的JNI视频,学习总结的一些基础知识。
1.JNIEnv
通过JNIEnv的指针能够对Java端的代码进行操作:
a.创建Java对象.
NewObject/NewString/New<Type>Array
b.调用Java对象的方法。
Call<Type>Method/CallStatic<Type>Method
c.获取及设置Java对象的属性。
Get/Set[Static]<Type>Method
2.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;
3.JType都继承自JObject
4.jobjectclass _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 {};
JNIEXPORT void JNICALL Java_com_jue_testnative_TestNative1_hello(JNIEnv *, jobject);
jobject指代的在Java中调用native方法的java类实例
5.获取jclass的方法
a. jclass FindClass(const char *name)
b. jclass GetObjectClass(jobject obj)
6.FindClass会在ClassPath下面寻找类。需要传入完整类名,注意包与包之间用’/'。
jclass class_str = env->FindClass("java/lang/String");
7.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, ...)
8.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)
返回值签名
9.javap -s 命令工具可以查看一个类的方法的签名
javap -s xxx.class
10.调用java方法
a.JNIEnv提供了Call<Type>Method, CallStatic<Type>Method,CallNonvirtual<Type>Method函数(能够实现子类对象调用父类方法的功能)。
b.调用实例方法的三种方法
1>Call<Type>Method(jobject obj, jmethodID id, ...);
boolean funcation(int i, double d, char c) {
}
env->CallBooleanMethod( obj, id_function , 100L, 3.44, L'3');
注意java中的int是C++环境中的长整形。L'3'是C++中的宽字符
2>Call<Type>MethodV(jobject obj, jmethodID id,va_list lst);
3>Call<Type>MethodA(jobject obj, jmethodID id, jvalue* v);
jvalue是一个联合体
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;
11.Java对象的创建
a.方式-NewObject
使用NewObject来创建Java对象。
jobject NewObject(jclass clazz, jmethodID methodID, ...)
需要先活的相应构造器的name,方法名设定为<init>,另外返回值的签名是Void
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
12.Java字符串<---->C++字符串
a.在java中字符串String对象是Unicode(UTF-16)码,每个字符无论中文还是英文都占用两个字节。
b.可以通过JNI接口把Java中的字符串转换成C/C++中的宽字符串,Java也可以传一个UTF-8的字符串(char*) 到C/C++中。
c.反过来C++可以通过把一个宽字符串,或者一个UTF-8的字符串来创建一个Java端的对象。
13.获取Java字符串
a.取得与某个jstring对象相关的Java字符串
方法 | 作用 |
GetStringChars | 取得UTF-16编码的宽字符串(char*) |
GetStringUTFChars | 取得UTF-8编码的字符串(char*) |
const jchar *GetStringChars(jstring str, jboolean *isCopy) {
return functions->GetStringChars(this,str,isCopy);
}
使用这两个方法,在不使用的时候,要注意使用ReleaseStringChars,或者ReleaseStringUTFChars释放拷贝的内存,或Java对象的引用。
void ReleaseStringChars(jstring str, const jchar *chars) {
functions->ReleaseStringChars(this,str,chars);
}
其中第一个参数表示,本地字符串的来源,第二个参数表示要释放的本地字符串
还可以使用下面的方法,这个方法可以增加返回jvm中java对象的可能性,但是还是有可能返回相应java串的拷贝。
这个方法没有相应的GetStringUTFCritical,由于Java字符串使用的是UTF-16,要转换成UTF-8编码的串本来就需要一次拷贝动作。
const jchar * GetStringCritical(jstring string, jboolean *isCopy) {
return functions->GetStringCritical(this,string,isCopy);
}
void ReleaseStringCritical(jstring string, const jchar *cstring) {
functions->ReleaseStringCritical(this,string,cstring);
}
GetStringCritical与ReleaseStringCritical不能使用JNI其他的函数(诸如给jvm分配其他对象)或导致当前线程等待的任何代码。否则会导致jvm的死锁。
另外一套方法,这个方法把java的字符串直接拷贝的C/C++的字符串数组中,在呼叫之前,必须有一个C/C++分配出来的字符串。
void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf) {
functions->GetStringRegion(this,str,start,len,buf);
}
void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) {
functions->GetStringUTFRegion(this,str,start,len,buf);
}
14.生成Java字符串
jstring NewString(const jchar *unicode, jsize len) {
return functions->NewString(this,unicode,len);
}
jstring NewStringUTF(const char *utf) { return functions->NewStringUTF(this,utf); }
注意,我们可以发现NewStringUTF中,没有字符串的长度,这是因为在C/C++中字符串结尾有一个附加的标志'\0'.
获取字符串的长度的方法
jsize GetStringLength(jstring str) {
return functions->GetStringLength(this,str);
}
jsize GetStringUTFLength(jstring str) {
return functions->GetStringUTFLength(this,str);
}
15.数组分为2种
a.基本类型的数组。
b.对象类型(Object[])的数组。
有一个通用于两种不同数组的的函数:
jsize GetArrayLength(jarray array) {
return functions->GetArrayLength(this,array);
}
16.处理基本类型数组
跟处理字符串相似
Get<Type>ArrayElements,可以把Java基本类型的数组转换成C/C++中的数组,可以拷贝一份传本地代码,也可以把指向Java中的指针直接传回本地代码。
需要用
Release<Type>ArrayElements
void ReleaseIntArrayElements(jintArray array,mode:
jint *elems,
jint mode) {
functions->ReleaseIntArrayElements(this,array,elems,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<Type>ArrayRegion 相似于GetStringRegion的函数:没有Release方法,因为我们是拷贝
void GetIntArrayRegion(jintArray array,
jsize start, jsize len, jint *buf) {
functions->GetIntArrayRegion(this,array,start,len,buf);
}
Set<Type>ArrayRegion 可以把指定范围内的Java数组元素用C++中的元素赋值。
void SetIntArrayRegion(jintArray array, jsize start, jsize len,
const jint *buf) {
functions->SetIntArrayRegion(this,array,start,len,buf);
}
创建一个基本类型的Java数组
<Type>Array New<Type>Array(jsize size),指定长度后返回相应Java基本类型的数组。
17.处理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);
}
18.java对象在JNI中的引用
在jvm中创建的对象被传到本地的C/C++代码的时候,会产生引用。根据jvm的垃圾回收机制,只要引用存在,就不会触发该引用指向对象的被回收。
这些引用分为三种
全局引用:Global Reference
局部引用:Local Reference
弱全局引用:Weak Global Reference
局部引用:是最常见的引用,局部引用只在native函数中有效。局部引用的存在会阻止其指向对象的回收。
全局引用:
可以跨越多个线程
在多个navtive方法中有效、
全局引用存在期间会阻止其引用java对象在jvm的回收。
全局引用的创建不是JNI自动创建的。
全局引用的创建要显示的调用NewGlobalRef函数,而释放需要调用ReleaseGlobalRef
弱全局引用:
与全局引用相似,创建和释放都需要由编程人员来进行。
多个线程,多个native方法中有效。
区别:这个引用不会阻止jvm回收其指向的引用。
NewWeakGlobalRef与ReleaseWeakGlobalRef来创建和释放引用。
19.IsSameObject在弱全局引用中有一个特别的功能。
把NULL传给要判断的object,来判断弱全局引用指向的java对象是否被回收。
20.缓存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" );}