Suppose I\'m embedding Sun\'s JVM in a C++ application. Through JNI I call a Java method (my own), which in turns calls a native method I implemented in a shared library.
Within the JNI literature, the word exception appears to be used exclusively to refer to Java exceptions. Unexpected events in native code are referred to as programming errors. JNI explicitly does not require JVMs to check for programming errors. If a programming error occurs, behavior is undefined. Different JVMs may behave differently.
It's the native code's responsibility to translate all programming errors into either return codes or Java exceptions. Java exceptions don't get thrown immediately from native code. They can be pending, only thrown once the native code returns to the Java caller. The native code can check for pending exceptions with ExceptionOccurred
and clear them with ExceptionClear
.
The Java compiler doesn't understand C++ exceptions, so you'll have to work with both Java and C++ exceptions. Luckily, that's not overly complicated. First we have a C++ exception that tells us if a Java exception has occurred.
#include <stdexcept>
//This is how we represent a Java exception already in progress
struct ThrownJavaException : std::runtime_error {
ThrownJavaException() :std::runtime_error("") {}
ThrownJavaException(const std::string& msg ) :std::runtime_error(msg) {}
};
and a function to throw an C++ exception if a Java exception is already in place:
inline void assert_no_exception(JNIEnv * env) {
if (env->ExceptionCheck()==JNI_TRUE)
throw ThrownJavaException("assert_no_exception");
}
we also have a C++ exception for throwing new Java exceptions:
//used to throw a new Java exception. use full paths like:
//"java/lang/NoSuchFieldException"
//"java/lang/NullPointerException"
//"java/security/InvalidParameterException"
struct NewJavaException : public ThrownJavaException{
NewJavaException(JNIEnv * env, const char* type="", const char* message="")
:ThrownJavaException(type+std::string(" ")+message)
{
jclass newExcCls = env->FindClass(type);
if (newExcCls != NULL)
env->ThrowNew(newExcCls, message);
//if it is null, a NoClassDefFoundError was already thrown
}
};
We also need a function to swallow C++ exceptions and replace them with Java exceptions
void swallow_cpp_exception_and_throw_java(JNIEnv * env) {
try {
throw;
} catch(const ThrownJavaException&) {
//already reported to Java, ignore
} catch(const std::bad_alloc& rhs) {
//translate OOM C++ exception to a Java exception
NewJavaException(env, "java/lang/OutOfMemoryError", rhs.what());
} catch(const std::ios_base::failure& rhs) { //sample translation
//translate IO C++ exception to a Java exception
NewJavaException(env, "java/io/IOException", rhs.what());
//TRANSLATE ANY OTHER C++ EXCEPTIONS TO JAVA EXCEPTIONS HERE
} catch(const std::exception& e) {
//translate unknown C++ exception to a Java exception
NewJavaException(env, "java/lang/Error", e.what());
} catch(...) {
//translate unknown C++ exception to a Java exception
NewJavaException(env, "java/lang/Error", "Unknown exception type");
}
}
With the above functions, it's easy to use Java/C++ hybrid exceptions in your C++ code, as shown below.
extern "C" JNIEXPORT
void JNICALL Java_MyClass_MyFunc(JNIEnv * env, jclass jc_, jstring param)
{
try { //do not let C++ exceptions outside of this function
//YOUR CODE BELOW THIS LINE. HERES SOME RANDOM CODE
if (param == NULL) //if something is wrong, throw a java exception
throw NewJavaException(env, "java/lang/NullPointerException", "param");
do_stuff(param); //might throw java or C++ exceptions
assert_no_exception(env); //throw a C++ exception if theres a java exception
do_more_stuff(param); //might throw C++ exceptions
//prefer Java exceptions where possible:
if (condition1) throw NewJavaException(env, "java/lang/NullPointerException", "condition1");
//but C++ exceptions should be fine too
if (condition0) throw std::bad_alloc("BAD_ALLOC");
//YOUR CODE ABOVE THIS LINE. HERES SOME RANDOM CODE
} catch(...) { //do not let C++ exceptions outside of this function
swallow_cpp_exception_and_throw_java(env);
}
}
If you're really ambitious, it's possible to keep track of a StackTraceElement[]
of your bigger functions, and get a partial stacktrace. The basic method is to give each function a StackTraceElement
, and as they're called, push a pointer to them onto a thread-local "callstack" and when they return, pop the pointer off. Then, alter the constructor of NewJavaException
to make a copy of that stack, and pass it to setStackTrace
.
I am guessing your JVM will crash. Native C++ exceptions do not propagate into Java through JNI. One reason for that is that JNI is a C interface, and C knows nothing of C++ exceptions.
What you have to do is catch the C++ exceptions before you get into the C layer of your JNI code, and make the JNI C function return an error code. Then you can check for the error code inside Java and throw a Java exception if necessary.
I would label that as undefined behavior. Propagation of exceptions back to C code (that's what is running the JVM) is undefined behavior.
On Windows, compilers have to use Microsoft's Structured Exception Handling to implement exceptions, so C++ exceptions will be "safely" caried through C code. However, that C code is not written with exceptions in mind, so you will get a crash if you're lucky, and inconsistent state and resource leaks if you aren't.
On other platforms, well, I don't know, but it can't be any prettier. When I write JNI code, I wrap every C++ function in a try
block: even if I don't throw
, I still might get some of the standard exceptions (std::bad_alloc
comes to mind, but others are possible too).
JNI uses c functions to interface with native code. C cannot handle exceptions correctly since it is not aware of their existence. So you have to catch the exceptions in your Native code and convert them to java exceptions or your jvm will crash. (This works since the java exception is only thrown once the native code returns to java)