当我从本机Java方法抛出C ++异常时会发生什么?

时间:2010-11-09 20:19:21

标签: java c++ linux exception java-native-interface

假设我将Sun的JVM嵌入到C ++应用程序中。通过JNI,我调用了一个Java方法(我自己的),它又调用了我在共享库中实现的本机方法。

如果此本机方法抛出C ++异常会发生什么?

编辑:编译器是gcc 3.4.x,jvm是sun的1.6.20。

5 个答案:

答案 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异常)