JNI相关笔记
1 生成native code所需要的头文件
- 首先使用javac对java文件进行编译
- 使用javah -jni [className],生成对应的头文件
- 创建cpp文件,实现所需要的函数
- 运行方式:
java -Djava.library.path=. HelloWorld
或者export LD_LIBRARY_PATH
2 JNI提供的一些函数和方法
-
String对象的获取
-
获取java传下来的String对象:
const jbyte* byteString = (*env)->GetStringUTFChars(env,str,NULL);
,由于该函数是对原来字符串的拷贝,所以使用完后需要对byteString进行NULL判断
使用完毕后,需要释放获取的对象:(*env)->ReleaseStringUTFChars(env,str,byteString);
-
GetStringRegion/GetStringUTFRegion
:会将字符串拷贝到预先申请的一片缓冲区里面(在栈上面,因此应避免大对象的拷贝),所以当使用这两个函数的时候,不需要进行NULL判断,也不需要进行Release操作 -
GetStringCritical/ReleaseStringCritical
:会尽可能直接使用String的指针(也有可能返回字符串的拷贝),且该方法会阻塞垃圾回收,因此使用的时候需当做临界资源,即在这两个函数中间不能使用JNI调用或者阻塞
-
-
类对象的获取
-
获取当前类的实例:
GetObjectClass:jclass cls = (*env)->GetObjectClass(env, obj)
获取和设置类中成员变量的方法:
jfieldID fid; fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;"); //cls为上一步获取的类的实例,GetStaticFieldID获取静态成员变量 if(!fid) { //需要判断是否获取成功 return; /* failed to find the field !*/ } jstring str = (*env)->GetObjectField(env, obj, fid); //获取jstring对象,获取完后就可以转换为const char*类型来操作了 jstring target = (*env)->NewStringUTF(env, "123"); if(!jstr) { return; /*out of memory */ } (*env)->SetObjectField(env, obj, fid, jstr); //设置对应的field
调用java类中的方法:
jclass cls = (*env)->GetObjectClass(env, obj); jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "()V"); if(!mid) { return; /* method not found */ } printf("In C "); //直接调用java对象中的方法,对应的还有CallIntMethod, CallStaticVoidMethod, CallStaticBooleanMethod (*env)->CallVoidMethod(env, obj, mid);
-
3 局部引用,全局引用,全局弱引用。
局部应用的错误示例:https://www.kancloud.cn/owenoranba/jni/120497 以为用static变量来存储findClass的结果,实际上由于findClass返回的局部引用,该引用指向的对象可能已经被销毁,所以这样的做法是无用的
正确的做法是用返回的局部引用创建一个GlobalRef:https://www.kancloud.cn/owenoranba/jni/120498
一般来讲局部引用会自动进行释放,但是这种情况下,最好手动释放局部引用:
for (int i = 0; i < len; ++i) {
jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
/* ... process jstr */
(*env)->DeleteLocalRef(env, jstr);
}
IsSameObject(jobject obj1,jobject obj2):
用来判断两个object是否引用的同一个对象,对于弱全局引用非常有效
EnsureLocalCapacity(jint capacity):
确保native函数在调用前有资源能够创建至少capacity个局部引用
(*env)->PushLocalFrame(env, 10)和result = (*env)->PopLocalFrame(env, result):
管理局部引用更高效的方式,示例:https://www.kancloud.cn/owenoranba/jni/120505
4 异常
当native代码调用java类中的方法的时候,有时候java类中可能会抛出异常,一般的检测方式为:
(*env)->CallVoidMethod(env, obj, mid);
//ExceptionCheck,更为高效的异常检查方式,返回值为boolean,只关心是否发生异常,不关心异常类的引用
exc = (*env)->ExceptionOccurred(env);
if(exc){
jclass newExcCls;
(*env)->ExceptionDescribe(env);
//打印出异常的描述信息
(*env)->ExceptionClear(env);//需要清理掉异常,为了下一次能够检测到
//省略部分代码
}
对于本地代码来讲,如果需要抛出异常,那么方式为:
jclass newExcCls;
newExcCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
if(!newExcCls){
/*Unable to find the exception class, give up. */
return;
}
(*env)->ThrowNew(env, newExcCls, "thrown from C code"); //抛出异常一个非常重要的一点,对于native代码来讲,ThrowNew方法并不会中断代码流程,所以需要手动去控制代码流程一个用于方便抛出异常的工具函数:
void JNU_ThrowByName(JNIEnv *env, const char* name, const char* msg){
jclass cls = (*env)->FindClass(env, name);
/*if cls is NULL, an exception has already been thrown */
if(cls){
(*env)->ThrowNew(env, cls, msg);
}
/* free the local ref */
(*env)->DeleteLocalRef(env, cls);//从此处也可以看出来,native代码中的ThrowNew是不影响流程的,需要自行控制
}