假设我将Sun的JVM嵌入到C ++应用程序中。通过JNI,我调用了一个Java方法(我自己的),它又调用了我在共享库中实现的本机方法。
如果此本机方法抛出C ++异常会发生什么?
编辑:编译器是gcc 3.4.x,jvm是sun的1.6.20。
答案 0 :(得分:24)
Java编译器不了解C ++异常,因此您必须使用Java和C ++异常。幸运的是,这并不是太复杂。首先,我们有一个C ++异常,告诉我们是否发生了Java异常。
#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) {}
};
以及在已经存在Java异常的情况下抛出C ++异常的函数:
inline void assert_no_exception(JNIEnv * env) {
if (env->ExceptionCheck()==JNI_TRUE)
throw ThrownJavaException("assert_no_exception");
}
我们还有一个C ++异常用于抛出新的Java异常:
//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
}
};
我们还需要一个函数来吞下C ++异常并用Java异常替换它们
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");
}
}
使用上述函数,可以很容易地在C ++代码中使用Java / C ++混合异常,如下所示。
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);
}
}
如果你真的雄心勃勃,可以跟踪StackTraceElement[]
更大的功能,并获得部分堆栈跟踪。基本方法是给每个函数一个StackTraceElement
,当它们被调用时,将指向它们的指针推到一个线程本地的“callstack”上,当它们返回时,弹出指针。然后,更改NewJavaException
的构造函数以制作该堆栈的副本,并将其传递给setStackTrace
。
答案 1 :(得分:5)
在JNI文献中,单词 exception 似乎仅用于引用Java异常。本机代码中的意外事件称为编程错误。 JNI显式不需要JVM来检查编程错误。如果发生编程错误,则行为未定义。不同的JVM可能表现不同。
本机代码有责任将所有编程错误转换为返回代码或Java异常。不会从本机代码中立即抛出Java异常。它们可以是 pending ,只有在本机代码返回给Java调用者后才会抛出。本机代码可以使用ExceptionOccurred
检查待处理的例外,并使用ExceptionClear
清除它们。
答案 2 :(得分:3)
我猜你的JVM会崩溃。本机C ++异常不会通过JNI传播到Java。其中一个原因是JNI是一个C接口,而C对C ++异常一无所知。
您需要做的是在进入JNI代码的C层之前捕获C ++异常,并使JNI C函数返回错误代码。然后,您可以检查Java中的错误代码,并在必要时抛出Java异常。
答案 3 :(得分:3)
我会将其标记为未定义的行为。将异常传播回C代码(即运行JVM的内容)是未定义的行为。
在Windows上,编译器必须使用Microsoft的结构化异常处理来实现异常,因此C ++异常将通过C代码“安全地”进行。 但是,C代码没有记住异常,所以如果你运气好的话就会崩溃,如果不是你就会出现状态和资源泄漏的不一致
在其他平台上,我不知道,但它不能更漂亮。当我编写JNI代码时,我将每个C ++函数包装在try
块中:即使我没有throw
,我仍然会得到一些标准异常(std::bad_alloc
,但其他人也是可能的)。
答案 4 :(得分:2)
JNI使用c函数与本机代码进行交互。 C无法正确处理异常,因为它不知道它们的存在。因此,您必须捕获Native代码中的异常并将它们转换为java异常,否则您的jvm将崩溃。 (这是有效的,因为只有在本机代码返回到java时才抛出java异常)