原文链接:http://blog.sina.com.cn/s/blog_53988c0c0100osnw.html
第六章 异常(CHAPTER 6 Exceptions)
在调用JNI函数后,在本地代码为可能出现的错误做检查中,我们遇到的许多情况。这章探讨本地代码怎样侦测和修复这些错误情况。
我们将关注作为"JNI"函数调用的结果的发生的错误,不是在本地代码中发生的任何错误(arbitrary errors)。如果一个本地调用操作系统功能,这只能简单使用记录文本的方法来在系统调用中可能的失败。另一方面,如果本地方法调用一个"Java API"方法的回调函数,使用在这章中详细描述的几步来恰当地检查和修复在方法执行中发生的可能的一场。
6.1 概要(Overview)
通过一系列例子,我们介绍"JNI"异常处理函数。
6.1.1 在本地方法中缓冲和抛出异常
下面的程序显示怎样声明一个抛出一个异常的本地方法。"CatchThrow"类声明一个"doit"本地方法,同时指定他抛出一个"IllegalArgumentException
class CathcThrow{
}
"CatchThrow.main"方法调用本地函数"doit",实现如下:
JNIEXPORT void JNICALL
Java_CatchThrow_doit(JNIEnv *env, jobject obj)
{
}
运行带本地库程序,如下输出:
java.lang.NullPointerException:
In Java:
这个回调方法抛出一个"NullPointerExcetption"。当"CallVoidMethod"返回控制给本地方法时,本地代码将通过"JNI"函数"ExceptionOccurred"侦测到这个异常.在我们例子中,当一个异常被侦测到,本地代码输出一个通过调用"ExceptionDescribe"的异常的详细信息,使用"ExceptionClear"来清除异常,同时抛出一个"IllegalArgumentException
通过"JNI"(例如,通过调用"ThrowNew")产生的一个未觉的异常,不会立即中断本地方法的执行。这和怎样处理在"Java"编程语言中的异常行为(exception behave)不一样。当一个异常在"Java"中被抛出时,虚拟器自动地改变控制流程到最近套装的"try/catch"声明来匹配异常类型。然后虚拟器清除未决异常和执行异常处理。对照相反(In contrast),"JNI"编程者必须清晰地实现控制流程,在一个异常已经发生后。
6.1.2 一个工具函数(A Utility Function)
抛出一个包含首次发现的异常类的异常,然后调用了"ThrowNew"函数。为了简单化这个任务,我们能写一个工具函数来抛出这个命名的异常:
void JNU_ThrowByName(JNIEnv *env), const char *name, const char *msg)
{
}
在这本书中,"JNU"前缀代表"JNI Utilities"("JNI"工具)。"JNU_ThrowByName"首席使用"FindClass"函数发现异常类型。如果"FindClass"失败(返回NULL),虚拟器必须抛出一个异常(例如"NoClassDefFoundError")。在这情况中,"JNU_ThrowByName"没有尝试抛出另一个异常。如果"FindClass"成功,我们抛出调用"ThrowNew"产生的命名异常。当"JNU_ThrowByName"放回,保证有个未决的异常,虽然这个未决异常不一定是名字参数指定的异常。我们保证了删除在这个函数创建的异常类型的局部引用。传地NULL给"DeleteLocalRef"是一个空操作,如果"FindClass"失败返回一个NULL,这是一个正确的行为。
6.2 恰当地异常处理
"JNI"编程者必须必须预知(foresee)可能的异常情况,同时写代码来检查和处理这些情况。适当地异常处理有时冗长乏味(tedious),但为了产生强壮的应用程序,它是必须的。
6.2.1 检查异常(Checking for Exceptions)
有两种方法检查是否有错误发生。
1.大多"JNI"函数使用一个清晰的(distinct)返回值(例如NULL)来指示一个错误发生。错误返回值也暗示(implies)在当前线程有个未解决的异常。(在返回值中编码错误条件是在C语言中常见的做法)
下面的例子说明使用NULL值,做为在错误检查中"GetFieldID"的返回值。这例子包含两个部分:一个类窗口(class Window)定义大量实例成员域(handle,length and width)和一个本地方法缓冲这些域的域ID。即使这些域存在于"Window"类中,我们任然需要检查可能从"GetFileID"返回的错误,因为虚拟器可能不能分配需要描述一个域ID(represent a field ID)的内存。
public class Window{
}
jfieldID FID_Window_handle ;
jfieldID FID_Window_length ;
jfieldID FID_Window_width ;
JNIEXPORT void JNICALL
Java_Window_initIDs(JNIEnv *env, jclass classWindow)
{
}
2.当使用一个"JNI"函数的返回值不能标记一个错误发生时,本地代码必须依赖产生异常来做错误检查。在当前线程中执行一个未决异常检测的"JNI"函数是"ExceptionOccurred"。("ExceptionCheck"被添加到"Java 2 SDK release 1.2"中。)例如,"JNI"函数"CallIntMethod"不能编码错误情况在返回值中。错误情况返回值的典型选择(Typical choices),例如"NULL"和"-1",不能工作, 因为他们可能是被调方法返回的合法值。思考一个"Fraction"类,这个类的"floor"f方法返回"fraction"的值的整数部分,同时一些本地代码调用了这个方法。
public class Fraction{
}
void f(JNIEnv *env, jobject fraction)
{
}
当"JNI"函数返回一个清晰的错误代码时,本地代码任然可以明显地通过调用,例如"ExceptionCheck",来检查异常。然而作为替代,它是更有效的检查明确的错误返回值。如果一个"JNI"函数返回它的错误值,在当前线程中的一个后续的"ExcptionCheck"调用保证返回"JNI_TRUE"。
6.2.2 处理异常(Handign Exceptions)
通过两种方式,本地代码可以处理一个未决的异常:
.本地方法实现能选择立即返回,引起异常在调用者中处理它。
.本地代码通过调用"ExceptionClear"能清理异常,然后执行它自己的异常处理代码。
在调用任何后续的JNI函数前,检查,处理和清楚一个未决的异常是非常重要(extremely important)。调用带有一个未决异常的大多数"JNI"函数(带有一个你没有清楚地清理异常)可能导致不期望的结果。当在当前线程中有一个未决的异常时,你能安全调用的JNI函数很少。11.8.2部分详细说明(specify)这些"JNI"函数的完整的清单。一般说,当这儿有一个未决的异常,你能调用被设计来处理异常的"JNI"函数,同时调用"JNI"函数来释放各种通过"JNI"暴露的虚拟器资源。
当异常发生的时候,释放资源经常是必须的。在下面的例子中,本地方法首先通过"GetStringChars"调用来获得字符串的内容。如果一个后续(subsequent)操作失败,需调用"ReleaseStringChars":
JNIEXPORT void JNICALL
Java_pkg_Cls_f(JNIEnv *env, jclass cls, jstring jstr)
{
}
当有个未决的异常,"ReleaseStringChars"的第一次被调用。本地方法的实现释放字符资源和事后立即返回没有首先清理异常。
6.2.3 在工具函数中的异常
写工具函数的编程者应该是花费特别的注意力在确保异常传播(propage)给本地方法的调用者.特别是(In particular),我们强调下面两个问题(issue):
.更合适地,工具函数应该提供一个特别的返回值来指示一个异常发生。者简化了调用者检查未决异常的任务。
.另外(In addition),工具函数在异常处理代码中应该按照规则(5.3部分)来管理局部引用。
为了说明,让我们介绍个工具函数,它是执行一个基于一个实例方法的名字和描述的回调。
jvalue
JNU_CallMethodByName(JNIEnv *env,
{
}
在其他参数中,"JNU_CallMethodByName"有个指向一个"jboolean"的指针。如果所有事都成功,这个"jboolean"被设置为"JNI_FALSE"。如果一个异常在这个函数的执行期间的任何位置发生,这个"jboolean"被设置为"JNI_TRUE"。这给"JNU_CallMethodByName"的调用者一个容易的方法来检测可能的异常。
"JNU_CallMethodByName"首先确信它能创建两个局部引用:一个类的引用和另一个为了从方法调用返回的结果。下一部,从"object"(对象)得到类的引用和查询方法ID。依靠返回类型,"switch"声明分发到对应的"JNI"方法调用函数。在回调返回后,如果"hasException"不是NULL,我们调用ExceptionCheck来检查未决异常。
"ExceptionCheck"函数是新加的在"Java 2 SDK release 1.2"中。相似的函数是"ExceptionOccurred"函数。不同的是"ExceptionCheck"没有返回异常对象的引用,但当有个未决的异常时返回"JNI_TRUE"和当没有未决异常时返回"JNI_FALSE"。当本地代码只需要知道是否有一个异常,但不需要得到这个异常对象的引用时,"ExceptionCheck"简化了局部引用管理。在"JDK release 1.1"中,前面的代码必须被写,如下:
if(hasException){
}
这额外的"DeleteLocalRef"调用是必须的,为了删除对异常对象的几部引用。
使用"JNU_CallMethodByName"函数,我们能重写"InstanceMethodCall.nativeMethod"的实现,在4.2部分,如下(as follows):
JNIEXPORT void JNICALL
Java_InstanceMehtodCall_nativeMethod(JNIEnv *env, jobject obj)
{
}
在"JNU_CallMethodByName"调用后,我们不需要要检查异常,因为本地方法后来立即返回(return immediately afterwards)。