【问题标题】:What happens when I throw a C++ exception from a native Java method?当我从本机 Java 方法抛出 C++ 异常时会发生什么?
【发布时间】:2011-05-07 11:51:05
【问题描述】:

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

如果这个本地方法抛出 C++ 异常会发生什么?

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

【问题讨论】:

  • 如果你让它传播,那么你可能会得到虚拟机“恐慌”响应。为什么不试试呢?
  • 什么平台?可能是特定于编译器的...
  • @Michael:我认为“Java”是一个平台?
  • @sbi Java 不是一个可以运行 C++ 的平台。

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


【解决方案1】:

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

我们还有一个用于抛出新 Java 异常的 C++ 异常:

//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,并在调用它们时,将指向它们的指针推送到线程本地“调用堆栈”,当它们返回时,将指针弹出。然后,更改NewJavaException 的构造函数以复制该堆栈,并将其传递给setStackTrace

【讨论】:

    【解决方案2】:

    我猜你的 JVM 会崩溃。本机 C++ 异常不会通过 JNI 传播到 Java。原因之一是 JNI 是一个 C 接口,而 C 对 C++ 异常一无所知。

    您要做的是在进入 JNI 代码的 C 层之前捕获 C++ 异常,并使 JNI C 函数返回错误代码。然后您可以检查 Java 内部的错误代码,并在必要时抛出 Java 异常。

    【讨论】:

    • 您可以使用 ThrowNew 函数在本机代码中引发 java 异常。存在java风格的解决方案时无需添加错误代码。
    • 你是对的。我不知道 ThrowNew。但是还是需要先捕获c++异常。
    【解决方案3】:

    JNI 使用 c 函数与本机代码交互。 C 不能正确处理异常,因为它不知道它们的存在。因此,您必须在 Native 代码中捕获异常并将它们转换为 java 异常,否则您的 jvm 将崩溃。 (这是可行的,因为只有在本机代码返回到 java 时才会抛出 java 异常)

    【讨论】:

      【解决方案4】:

      在 JNI 文献中,exception 一词似乎专门用于指代 Java 异常。本机代码中的意外事件称为编程错误。 JNI 明确不要求 JVM 检查编程错误。如果发生编程错误,则行为未定义。不同的 JVM 可能表现不同。

      将所有编程错误转换为返回码或 Java 异常是本机代码的职责。 Java 异常不会立即从本机代码中抛出。它们可以是待处理,仅在本机代码返回给 Java 调用者时才抛出。本机代码可以使用ExceptionOccurred 检查未决异常并使用ExceptionClear 清除它们。

      【讨论】:

        【解决方案5】:

        我会将其标记为未定义的行为。将异常传播回 C 代码(即运行 JVM 的代码)是未定义的行为。

        在 Windows 上,编译器必须使用 Microsoft 的结构化异常处理来实现异常,因此 C++ 异常将通过 C 代码“安全”地处理。 然而,C 代码在编写时没有考虑到异常,所以如果你幸运的话,你会遇到崩溃,如果你不幸运的话,就会出现不一致的状态和资源泄漏.

        在其他平台上,嗯,我不知道,但它不能更漂亮。当我编写 JNI 代码时,我将每个 C++ 函数包装在一个 try 块中:即使我没有 throw,我仍然可能会遇到一些标准异常(想到 std::bad_alloc,但其他的也可能)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-12-12
          • 1970-01-01
          • 1970-01-01
          • 2011-03-20
          • 2011-03-25
          • 2017-02-10
          • 1970-01-01
          • 2011-10-13
          相关资源
          最近更新 更多