How to catch JNI/Java Exception
I have a JNI layer in my application. In some cases Java throws an exception. How can I get the Java exception in the JNI layer? I have the code something like as follows.
if((*(pConnDA->penv))->ExceptionCheck(pConnDA->penv))
{
(*(pConnDA->penv))->ExceptionDescribe(pConnDA->penv);
(*(pConnDA->penv))->ExceptionClear(pConnDA->penv);
}
Will this block of code catch only JNI exceptions? Where will the exception description be logged in console(stderr)? How do I get this into the buffer, so that I can pass it to my logger module?
Solution 1:
if you invoke a Java method from JNI, calling ExceptionCheck
afterwards will return JNI_TRUE
if an exception was thrown by the Java.
if you're just invoking a JNI function (such as FindClass
), ExceptionCheck
will tell you if that failed in a way that leaves a pending exception (as FindClass
will do on error).
ExceptionDescribe
outputs to stderr. there's no convenient way to make it go anywhere else, but ExceptionOccurred
gives you a jthrowable
if you want to play about with it, or you could just let it go up to Java and handle it there. that's the usual style:
jclass c = env->FindClass("class/does/not/Exist");
if (env->ExceptionCheck()) {
return;
}
// otherwise do something with 'c'...
note that it doesn't matter what value you return; the calling Java code will never see it --- it'll see the pending exception instead.
Solution 2:
This is a complement of the Elliott Hughes' answer. My answer provides a step-by-step example explaining how to catch exceptions and how to translate them between C++ and Java words using the JNI layer.
Short answer
See the correct Elliott Hughes' answer.
Reusable example
This answer and snippets are in public domain or in CC0 in order to ease reuse. All the source code here is C++03 backward compatible.
To reuse the above snippet do the following:
- Replace
mypackage::Exception
by your own C++ Exception. - If the corresponding Java exception
my.group.mypackage.Exception
is not defined, then replace"my/group/mypackage/Exception"
by"java/lang/RuntimeException"
.
Catch exceptions from Java
See also the snippet on coliru.
void rethrow_cpp_exception_as_java_exception()
{
try
{
throw; // This allows to determine the type of the exception
}
catch (const mypackage::Exception& e) {
jclass jc = env->FindClass("my/group/mypackage/Exception");
if(jc) env->ThrowNew (jc, e.what());
/* if null => NoClassDefFoundError already thrown */
}
catch (const std::bad_alloc& e) {
jclass jc = env->FindClass("java/lang/OutOfMemoryError");
if(jc) env->ThrowNew (jc, e.what());
}
catch (const std::ios_base::failure& e) {
jclass jc = env->FindClass("java/io/IOException");
if(jc) env->ThrowNew (jc, e.what());
}
catch (const std::exception& e) {
/* unknown exception (may derive from std::exception) */
jclass jc = env->FindClass("java/lang/Error");
if(jc) env->ThrowNew (jc, e.what());
}
catch (...) {
/* Oops I missed identifying this exception! */
jclass jc = env->FindClass("java/lang/Error");
if(jc) env->ThrowNew (jc, "Unidentified exception => "
"Improve rethrow_cpp_exception_as_java_exception()" );
}
}
I thanks Mooing Duck for contribution on the above C++ code.
Adapt the JNI generated source code
The following file Java_my_group_mypackage_example.cpp
use the above rethrow_cpp_exception_as_java_exception()
function:
JNIEXPORT jlong JNICALL Java_my_group_mypackage_example_function1
(JNIEnv *env, jobject object, jlong value)
{
try {
/* ... my processing ... */
return jlong(result);
} catch(...) {
rethrow_cpp_exception_as_java_exception();
return 0;
}
}
JNIEXPORT jstring JNICALL Java_my_group_mypackage_example_function2
(JNIEnv *env, jobject object, jlong value)
{
jstring jstr = 0
try {
/* ... my processing ... */
jstr = env->NewStringUTF("my result");
} catch(...) {
rethrow_cpp_exception_as_java_exception();
}
return jstr;
}
JNIEXPORT void JNICALL Java_my_group_mypackage_example_function3
(JNIEnv *env, jobject object, jlong value)
{
try {
/* ... my processing ... */
} catch(...) {
rethrow_cpp_exception_as_java_exception();
}
}
Corresponding Java code
File example.java
package my.group.mypackage;
public class Example {
static {
System.loadLibrary("my-DLL-name");
}
public Example() {
/* ... */
}
private native int function1(int); //declare DLL functions
private native String function2(int); //using the keyword
private native void function3(int); //'native'
public void dosomething(int value) {
int result = function1(value);
String str = function2(value); //call your DLL functions
function3(value); //as any other java function
}
}
Note: "my-DLL-name
" refers to the dynamic library produced from the above C/C++ code compiled. It can be my-DLL-name.dll
on Windows or my-DLL-name.so
on GNU/Linux/Unix.
Steps
Generate
example.class
fromexample.java
(usingjavac
or maven or your favorite IDE Eclipse/Netbeans/IntelliJ IDEA/...)Generate C/C++ header file
Java_my_group_mypackage_example.h
fromexample.class
usingjavah