在编写工具函数时候应当注意把工具函数中可能发生的异常通过某种方式暴露给调用者,使他们能够处理这些异常。特别是在以下两种场景下:
* 工具函数最好提供一个特殊的返回值来标识函数中发生的异常,可以让调用者方便的检查异常。
* 另外,工具函数在发生异常的时候,应当按照5.3节描述的规则来管理局部引用。
让我们通过一个例子来更清楚的了解这两种状况,以下是一个工具函数,通过一个对象示例方法描述符和名字属性调用一个Java函数:
~~~
jvalue JNU_CallMethodByName(JNIEnv *env, jboolean * hasException, jobject obj, const char * name, const char*descriptor, ...){
va_list args;
jclass clazz;
jmethodID mid;
jvalue result;
if((*env)->EnsureLocalCapacity(env, 2) == JNI_OK) {
clazz = (*env)->GetObjectClass(env, obj);
mid = (*env)->GetMethodID(env, clazz, name, descriptor);
if(mid){
const char *p = descriptor;
/*skip over argument types to find out the return type */
while(*p != ')') p++;
/* skip ')' */
p ++;
va_start(args, descriptor);
switch(*p){
case 'V':
(*env)->CallVoidMethod(env, obj, mid, args);
break;
case '[':
case 'L':
result.l = (*env)->CallObjectMethodV(env, obj, mid, args);
break;
case 'Z':
result.z = (*env)->CallBooleanMethodV(env, obj, mid, args);
break;
case 'B':
result.b = (*env)->CallByteMethodV(env, obj, mid, args);
break;
case 'C':
result.c = (*env)->CallCharMethodV(env, obj, mid, args);
break;
case 'S':
result.s = (*env)->CallShortMethodV(env, obj, mid, args);
break;
case 'I':
result.i = (*env)->CallIntMethodV(env, obj, mid, args);
break;
case 'J':
result.j = (*env)->CallLongMethodV(env, obj, mid, args);
break;
case 'F':
result.f = (*env)->CallFloatMethodV(env, obj, mid, args);
break;
case 'D':
result.d = (*env)->CallDoubleMethodV(env, obj, mid, args);
break;
default:
(*env)->FatalError(env, "illegal descriptor");
}
va_end(args);
}
(*env)->DeleteLocalRef(env, clazz);
}
if(hasException){
*hasException = (*env)->ExceptionCheck(env);
}
return result;
}
~~~
JNU_CallMethodByName使用了一个额外的jboolean类型的参数hasException用于保存函数执行过程是否出现异常的标志位,如果所有操作都成功,则被设置为JNI_FALSE, 否则被设置为JNI_TRUE.
JNU_CallMethodByName首先检查了是能够创建两个局部引用:一个为了保存类引用,另一个则为了保存函数调用的返回值。然后,获取对象对应的类并查找函数对应的函数ID。接下来在switch处理流程中根据返回值类型调用不同的JNI函数。最后,如果hasException不为空,则调用ExceptionCheck检查异常并将值赋值给hasException.
ExceptionCheck函数是在Java 2 SDK release 1.2中加入的,它与ExceptionOccurred函数的作用类似,不同之处在于ExceptionCheck函数不会返回异常对象的引用,而是返回一个异常是否发生的jboolean标识.所以,当调用者不关心异常的类型,仅仅关心是否发生了异常的时候,使用它会很方便而且高效。上边异常检查的代码如果用ExceptionOccurred重写如下:
~~~
if(hasException) {
jthrowable exc = (*env)->ExceptionOccurred(env);
*hasException = exc != NULL;
(*env)->DeleteLocalRef(env, exc);
}
~~~
如上代码,需要额外的调用DeleteLocalRef来删除指向异常对象的引用。
使用JNU_CallMethodByName函数,我们可以重写4.2节的InstanceMethodCall.nativeMethod如下:
~~~
JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj){
printf("In C\n");
JUN_CallMethodByName(env, NULL, obj, "callback", "()V");
}
~~~
我们不需要再JNU_CallMethodByName调用之后检查异常因为这个函数会在调用JNU_CallMethodByName之后立刻返回。
- JNI编程指南翻译
- 第一部分:介绍与入门
- 介绍
- Java平台与宿主环境
- JNI的角色
- 使用JNI的潜在风险
- 什么时候应该使用JNI
- JNI的进化
- 示例程序
- 入门
- 概览
- 声明一个Native方法
- 编译HelloWorld类
- 创建Native方法头文件
- 实现Native方法
- 编译C源码并创建一个Native库
- 执行程序
- 第二部分:编程指南
- 基础类型, Strings, Arrays
- 一个简单的Native方法
- Native方法实现的C语言原型
- Native方法的参数
- 类型映射
- 访问Strings
- 转换成为Native Strings
- 释放Native Strings资源
- 创建新的Strings
- String相关的其他JNI函数
- Java SDK2 中的新的String相关的JNI方法
- JNI String函数总结
- 在String函数中选择
- 访问Arrays
- 在C语言中操作数组
- 访问基础类型的数组
- JNI基础类型数组操作函数总结
- 如何在基础类型数组函数中做选择
- 访问对象数组
- 属性和方法
- 访问属性
- 如何访问一个对象实例的属性
- 属性描述符
- 访问静态属性
- 调用方法
- 调用对象实例方法
- 获取方法描述符
- 调用静态方法
- 调用对象的基类方法
- 调用构造函数
- 缓存属性和方法ID
- 在使用时缓存
- 在类初始化的时候缓存
- 两种ID缓存方式的对比
- JNI属性和方法操作的性能
- 局部和全局引用
- 局部和全局引用说明
- 局部引用
- 全局引用
- 弱全局引用
- 引用的比较
- 引用资源的释放
- 释放局部引用
- JDK2中的局部引用管理
- 释放全局引用
- 管理引用的原则
- 异常
- 概览
- 在Native代码中捕获与抛出异常
- 工具函数
- 异常处理最佳实践
- 异常检查
- 异常处理
- 工具函数中的异常
- The Invocation Interface
- 创建Java虚拟机
- 链接ava虚拟机到Native应用程序
- 链接某个确定的虚拟机实现
- 链接未知虚拟机
- 绑定Native线程
- 一些JNI特性
- JNI与线程
- 约束
- 监视进入与退出
- 监视等待和通知
- 如何从当前Context中获取JNIEnv的指针
- 匹配线程模型
- 编写国际化代码
- 注册Native方法
- 加载与卸载时机
- 加载时机
- 卸载时机
- 反射的支持
- 使用C++实现JNI
- 改变现有的Native库
- 一对一映射关系
- 共享桩
- 一对一映射VS共享桩
- 共享桩的实现
- Peer类
- Java平台中的Peer类
- 释放Native数据结构
- Peer类背后的指针
- 缺陷与陷阱
- 错误检查
- 向JNI函数传递非法参数
- jclass与jobject的困惑
- jboolean类型参数的截断机制
- Java应用与Native代码之间的边界
- ID与引用的困惑
- 缓存属性和方法ID
- 终结Unicode 字符串
- Violating访问控制原则
- 无视国际化
- 保持虚拟机资源
- 被滥用的局部引用创建
- 跨线程使用JNIEnv
- 错误使用的线程模型
- 第三部分:详细说明
- JNI设计概览
- 设计目标
- 加载Native库
- 类加载器
- 类加载器与Native库
- 加载Native库
- 类型安全限制
- 卸载Native库
- 链接Native方法
- 调用转换
- JNIEnv接口指针
- JNIEnv接口指针的组织
- 接口指针的好处
- 传递数据
- 全局和局部引用
- 局部引用的实现
- 弱全局引用
- 访问对象
- 访问原始数组
- 属性和方法
- 错误和异常
- 如果没有对编码错误的检查
- Java虚拟机异常
- 异步异常
- JNI类型
- 原始类型和引用类型
- 原始类型
- 引用类型
- jvalue类型
- 属性和方法ID
- 字符串格式
- UTF-8字符串
- 类描述符
- 属性描述符
- 方法描述符
- 常量
- JNI函数
- JNI函数总结
- 直接导出调用的接口函数
- Java虚拟机接口
- 在Native库中定义的函数
- JNIEnv接口
- JNI函数使用规范