Exception handling in JNI






4.50/5 (4 votes)
Feb 8, 2007
4 min read

79047

441
An article on Exception handling in JNI
Introduction
Exception handling generally makes error reporting and error handling more readable and easier. But it will be trickier if more than one programming language is used. This article explains exception handling in JNI (Java Native Interface).
Exception handling
Here we are going to look at two ways of handling the exception in JNI. The first approach is the standard C++ exception handling. That is, if any exception happens, we will catch it and take the necessary steps on the C++ side.
try
{
throw "this is a demo try catch exception ";
}
catch (char *exception)
{
printf("An exception happened. Details: %s",exception);
//Handle the exception.
}
But think about a situation where some serious error has occurred (memory allocation failed, or unable to open a file which is necessary to start the application). Here we have to notice the Java world, saying that "I can't proceed further" with the complete details (the file name in which the exception occurred, the line number and the error message).
The following code does that:
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_); \
Here '__FILE__
' and '__LINE__
' are predefined macros and part of the C/C++ standard. During preprocessing, they are replaced respectively by a constant string holding the current file name and by an integer representing the current line number.
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);
}
Error handling with ASSERT
Assert mechanism is one of the good methods to check the possibilities of errors in our code. Assert contains a conditional statement and an error message. i.e., if the condition is false
, there is an error in the method and we will display the error message. I created a simple assert, we can call it "JNI_ASSERT
" for our purpose.
#define JNI_ASSERT(_EXPRESSION_ ,_INFO_) \
if (!(_EXPRESSION_)) \
//Report Error
e.g : JNI_ASSERT(pzExpr != NULL, "Invalid null pointer" ) ;
Here if the condition "pzExpr != NULL
" is false
, (pzExpr equals null )
, the assert happens. If the condition is true
, the program continues to execute.
One of the issues in exception handling is retaining the consistency of the data and object if the method fails. When a method fails, it is expected that the method should leave the objects and data in the same state as it was before. It is easy for a method to leave the objects in an inconsistent state, because of the operation invoked by this method.
So here the idea is save the program state in the beginning of the JNI method and if any assert happens, we will restore the saved sate and throw a Java exception
. This is done by using setjmp
and longjmp
.
About setjmp/longjmp
The setjmp
function saves the state of a program. The state of a program includes the values of stack pointer , frame pointer, program counter. A program state is completely defined by these set of registers and the contents of memory, which includes the heap and the stack. We can restore the saved state by longjmp
.
int setjmp (jmp_buf env);
int longjmp(jmp_buf env , int val);
setjmp
stores the state of the program in a variable of type jmp_buf
(defined in the header file setjmp.h). The longjmp
function restores the state of the program that is stored in env
and the program execution resumes at the instruction after setjmp
. One important point here is, before encountering a longjmp
there has to be a setjmp
which saves the state in env
and returns a value 0
.
How it works!
The idea is to save the program state in each entry point of the JNI function and when any assert happens, we will restore the saved state back and throw an exception to Java with the error details. The following code does that.
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).
In the entry point of the JNI function, the program state is saved by using the macro SAVE_PGM_STATE
. Now here all the magic happens!. The call to setjmp(g_sJmpbuf)
saves the program state and it returns zero. i.e. the "if
" condition fails. Now when the assert happens, it calls the function RestoreProgramState
which in turn calls longjmp
. Now this causes the setjmp
being executed again and the "if
" condition being true
and the method ThrowJNIException
is called.
The main disadvantage of this method is the cleanup (closing file descriptors, flushing buffers, freeing heap-allocated memory, etc).
About the Java code
JniException<code><code>
extends RuntimeExceptionThis class is used to handle the JNI Exception.TestJniException
This is the main class which test theJNIException
. It loads thejni
library and call the native functions. The interesting function may be the one which displays themessagebox
.
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
The JNI
library is created under Visual Studio. All the files are included in the attachment. You can build the project without any changes. You can build the library under Linux also, if you have the jni
header files.
g++ -o libJniException.so jni_exception.cpp -shared -I$(JNI_INCLUDE).
Here you have to specify the JNI_INCLUDE
for the jni
header files.
The Java project is done in Eclipse. You can import the project into Eclipse and use it *File->import->Existing project into workspace and then specify the directory where you copied the source.
If you want to run the application without SWT, you can comment the SWT messagebox code and printout the exception details in the standard output.