JNI 中的异常处理






4.50/5 (4投票s)
2007年2月8日
4分钟阅读

81505

463
关于 JNI 中异常处理的文章。
引言
异常处理通常使错误报告和错误处理更具可读性且更轻松。但如果使用多种编程语言,情况会更棘手。本文解释了 JNI(Java Native Interface)中的异常处理。
异常处理
在这里,我们将探讨 JNI 中处理异常的两种方法。第一种方法是标准的 C++ 异常处理。也就是说,如果发生任何异常,我们将捕获它并在 C++ 端采取必要的步骤。
try
{
throw "this is a demo try catch exception ";
}
catch (char *exception)
{
printf("An exception happened. Details: %s",exception);
//Handle the exception.
}
但请考虑一种情况,即发生了一些严重的错误(内存分配失败,或无法打开应用程序启动所必需的文件)。此时,我们必须通知 Java 世界,“我无法继续进行”,并附带完整的详细信息(发生异常的文件名、行号和错误消息)。
下面的代码实现了这一点
try
{
//Allocate some memory
int* myarray= new int[10000];
}
catch (bad_alloc&) //Memory allocation exception !
{
//Inform java world !
THROW_JAVA_EXCEPTION ("Error allocating memory.\n");
}
#define THROW_JAVA_EXCEPTION (_INFO_) \
ThrowJNIException(__FILE__,__LINE__, \
_INFO_); \
这里的 '__FILE__
' 和 '__LINE__
' 是预定义的宏,是 C/C++ 标准的一部分。在预处理期间,它们分别被当前文件名常量字符串和表示当前行号的整数替换。
void ThrowJNIException(const char* pzFile, int iLine,const char* pzMessage)
{
//Creating the error messages
if(pzFile != NULL && pzMessage != NULL && iLine != 0)
sprintf(g_azErrorMessage,"JNIException ! \n \
File \t\t: %s \n \
Line number \t\t: %d \n \
Reason for Exception\t: %s ",pzFile,iLine,pzMessage);
jclass tClass = NULL;
JNIEnv *env;
//Get the JNIEnv by attaching to the current thread.
cached_jvm->AttachCurrentThread( (void **)&env, NULL );
//Check for null. If something went wrong, give up
if( env == NULL) {
printf("Invalid null pointer in ThrowJNIException " );
return;
}
//Find the exception class.
tClass = env->FindClass(JNI_EXCEPTION_CLASS_NAME);
if (tClass == NULL) {
printf("Not found %s",JNI_EXCEPTION_CLASS_NAME);
return;
}
//Throw the exception with error info
env->ThrowNew(tClass,g_azErrorMessage );
env->DeleteLocalRef(tClass);
}
使用 ASSERT 进行错误处理
断言机制是检查代码中潜在错误的好方法之一。断言包含一个条件语句和一个错误消息。即,如果条件为 false
,则方法中存在错误,我们将显示错误消息。我创建了一个简单的断言,我们可以称之为“JNI_ASSERT
”以供我们使用。
#define JNI_ASSERT(_EXPRESSION_ ,_INFO_) \
if (!(_EXPRESSION_)) \
//Report Error
e.g : JNI_ASSERT(pzExpr != NULL, "Invalid null pointer" ) ;
在这里,如果条件“pzExpr != NULL
”为 false
(pzExpr 为 null
),则发生断言。如果条件为 true
,则程序继续执行。
异常处理的一个问题是在方法失败时保留数据和对象的一致性。当方法失败时,期望该方法应将对象和数据保持在调用之前相同的状态。方法很容易将对象留在不一致的状态,这是由于该方法调用的操作所致。
因此,这里的想法是在 JNI 方法的开头保存程序状态,如果发生任何断言,我们将恢复保存的状态并抛出一个 Java exception
。这是通过使用 setjmp
和 longjmp
来完成的。
关于 setjmp/longjmp
setjmp
函数保存程序的状态。程序状态包括堆栈指针、帧指针、程序计数器的值。程序状态由这些寄存器集以及内存内容(包括堆和栈)完全定义。我们可以使用 longjmp
恢复保存的状态。
int setjmp (jmp_buf env);
int longjmp(jmp_buf env , int val);
setjmp
将程序状态存储在类型为 jmp_buf
(在头文件 setjmp.h 中定义)的变量中。longjmp
函数恢复存储在 env
中的程序状态,程序执行在 setjmp
之后的指令处继续。这里一个重要的点是,在遇到 longjmp
之前,必须有一个 setjmp
将状态保存在 env
中并返回一个值 0
。
它的工作原理!
其思想是在 JNI 函数的每个入口点保存程序状态,并在发生任何断言时,恢复已保存的状态并向 Java 抛出带有错误详细信息的异常。下面的代码实现了这一点。
JNIEXPORT void JNICALL
Java_org_tutorial_jni_util_exception_TestJniException_testexception
(JNIEnv *env, jobject object)
{
//Save the program state..
SAVE_PGM_STATE();
//
//...........
//
JNI_ASSERT(0,"This is a demo exception from c++ !");
//
//...........
//
}
/*Save the program state. This saved state can be restored by longjmp*/
#define SAVE_PGM_STATE()\
if (setjmp(g_sJmpbuf)!=0 )\ //Save the program state(the first
//time) and restore it(second time)
{\
ThrowJNIException();\ //Throw the java exception.
return; \
}\
/*Assert definition.
When the condition fails, call the function with the file,line,
and the error information.*/
#define JNI_ASSERT(_EXPRESSION_ ,_INFO_) \
if (!(_EXPRESSION_)) \ //Validate the expression.
RestoreProgramState(__FILE__,__LINE__, \
_INFO_); \
/* Restore the saved state by calling longjmp(RESTORE_SAFE_STATE)*/
void RestoreProgramState(const char* pzFile, int iLine,const char* pzMessage)
{
//Copy the error message to the global array.
sprintf(g_azErrorMessage,"JNIException ! \n \
File \t\t: %s \n \
Line number \t\t: %d \n \
Reason for Exception\t: %s ",pzFile,iLine,pzMessage);
//Restore the saved/safe state.
RESTORE_SAFE_STATE();
}
/*
Restore the program state into the saved state
(the program state is saved by setjmp)*/
#define RESTORE_SAFE_STATE() \
longjmp(g_sJmpbuf,1);\ //Restore the program state. (this calls
//setjmp second time).
在 JNI 函数的入口点,通过使用宏 SAVE_PGM_STATE
保存程序状态。现在,这里发生了一切魔术!调用 setjmp(g_sJmpbuf)
会保存程序状态并返回零。也就是说,“if
”条件失败。现在,当断言发生时,它会调用 RestoreProgramState
函数,该函数又会调用 longjmp
。这会导致 setjmp
再次执行,并且“if
”条件为 true
,然后调用 ThrowJNIException
方法。
此方法的主要缺点是清理(关闭文件描述符、刷新缓冲区、释放堆分配的内存等)。
关于 Java 代码
JniException<code><code>
扩展了 RuntimeException。此类用于处理 JNI 异常。TestJniException
这是测试JNIException
的主类。它加载jni
库并调用本机函数。可能有趣的函数是显示messagebox
的函数。
public void MsgBox(Exception e)
{
//Get the stack trace and the details from c++
StackTraceElement st[] = e.getStackTrace();
StringBuffer errorMessage = new StringBuffer(e.getMessage()
+ "\n\nJava StackTrace :\n\n");
for (int i = 0; i < st.length; i++)
errorMessage.append(st[i].toString() + "\n");
Display display = new Display();
Shell shell = new Shell(display);
MessageBox messageBox = new MessageBox(shell, SWT.OK | SWT.ICON_ERROR);
messageBox.setText("JNI Exception !");
messageBox.setMessage(errorMessage.toString());
messageBox.open();
}
Using the Code
JNI
库是在 Visual Studio 下创建的。所有文件都包含在附件中。您可以直接构建项目。如果您拥有 jni
头文件,也可以在 Linux 下构建该库。
g++ -o libJniException.so jni_exception.cpp -shared -I$(JNI_INCLUDE).
在这里,您必须为 jni
头文件指定 JNI_INCLUDE
。
Java 项目在 Eclipse 中完成。您可以将项目导入 Eclipse 并使用它:“File->import->Existing project into workspace”,然后指定复制源的目录。
如果您想在没有 SWT 的情况下运行应用程序,您可以注释掉 SWT 消息框代码,并将异常详细信息打印到标准输出。