65.9K
CodeProject 正在变化。 阅读更多。
Home

JNI 中的异常处理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (4投票s)

2007年2月8日

4分钟阅读

viewsIcon

81505

downloadIcon

463

关于 JNI 中异常处理的文章。

Sample image

引言

异常处理通常使错误报告和错误处理更具可读性且更轻松。但如果使用多种编程语言,情况会更棘手。本文解释了 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”为 falsepzExpr 为 null),则发生断言。如果条件为 true,则程序继续执行。

异常处理的一个问题是在方法失败时保留数据和对象的一致性。当方法失败时,期望该方法应将对象和数据保持在调用之前相同的状态。方法很容易将对象留在不一致的状态,这是由于该方法调用的操作所致。

因此,这里的想法是在 JNI 方法的开头保存程序状态,如果发生任何断言,我们将恢复保存的状态并抛出一个 Java exception。这是通过使用 setjmplongjmp 来完成的。

关于 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 消息框代码,并将异常详细信息打印到标准输出。

© . All rights reserved.