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 copyclass _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提供了一下函函数(能够实现子类对象调用父类方法的功能)
- CallMethod,
- CallStaticMethod,
CallNonvirtualMethod。
CallVoidMethod(jobject obj, jmethodID methodID, ...) void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args) void CallVoidMethodA(jobject obj, jmethodID methodID, jvalue* args)复制代码
调用实例方法的三种方法
CallMethod(jobject obj, jmethodID id, ...);
boolean funcation(int i, double d, char c) { } env->CallBooleanMethod( obj, id_function , 100L, 3.44, L'3');复制代码
CallMethodV(jobject obj, jmethodID id,va_list lst);
CallMethodA(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,方法名设定为,另外返回值的签名是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,"","()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
处理基本类型数组
跟处理字符串相似
GetArrayElements,可以把Java基本类型的数组转换成C/C++中的数组,可以拷贝一份传本地代码,也可以把指向Java中的指针直接传回本地代码。
需要用
ReleaseArrayElements
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); }复制代码
GetArrayRegion 相似于GetStringRegion的函数:没有Release方法,因为我们是拷贝
void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint *buf) { functions->GetIntArrayRegion(this,array,start,len,buf); }复制代码
SetArrayRegion 可以把指定范围内的Java数组元素用C++中的元素赋值。
void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint *buf) { functions->SetIntArrayRegion(this,array,start,len,buf); }复制代码
创建一个基本类型的Java数组
Array NewArray(jsize size),指定长度后返回相应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
微信公众号:终端研发部