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);
}
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)
{
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;
cached_jvm->AttachCurrentThread( (void **)&env, NULL );
if( env == NULL) {
printf("Invalid null pointer in ThrowJNIException " );
return;
}
tClass = env->FindClass(JNI_EXCEPTION_CLASS_NAME);
if (tClass == NULL) {
printf("Not found %s",JNI_EXCEPTION_CLASS_NAME);
return;
}
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_)) \
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_PGM_STATE();
JNI_ASSERT(0,"This is a demo exception from c++ !");
}
#define SAVE_PGM_STATE()\
if (setjmp(g_sJmpbuf)!=0 )\ {\
ThrowJNIException();\ return; \
}\
#define JNI_ASSERT(_EXPRESSION_ ,_INFO_) \
if (!(_EXPRESSION_)) \ RestoreProgramState(__FILE__,__LINE__, \
_INFO_); \
void RestoreProgramState(const char* pzFile, int iLine,const char* pzMessage)
{
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_SAFE_STATE();
}
#define RESTORE_SAFE_STATE() \
longjmp(g_sJmpbuf,1);\
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 the JNIException
. It loads the jni
library and call the native functions. The interesting function may be the one which displays the messagebox
.
public void MsgBox(Exception e)
{
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.