Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Exception handling in JNI

4.50/5 (4 votes)
7 Feb 20074 min read 1   441  
An article on Exception handling in JNI

Sample image

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.

C++
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.

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);
}

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.

C++
#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.

C++
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.

C++
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 the JNIException. It loads the jni library and call the native functions. The interesting function may be the one which displays the messagebox.

C++
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here