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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4138168/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 23:55:17  来源:igfitidea点击:

What happens when I throw a C++ exception from a native Java method?

javac++linuxexceptionjava-native-interface

提问by Idan K

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.

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

What happens if this native method throws a C++ exception?

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

edit: compiler is gcc 3.4.x, jvm is sun's 1.6.20.

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

采纳答案by Rob Kennedy

Within the JNI literature, the word exceptionappears 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.

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

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 ExceptionOccurredand clear them with ExceptionClear.

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

回答by Dima

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.

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

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.

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

回答by André Caron

I would label that as undefined behavior. Propagation of exceptions back to C code (that's what is running the JVM) is undefined behavior.

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

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.

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

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 tryblock: even if I don't throw, I still might get some of the standard exceptions (std::bad_alloccomes to mind, but others are possible too).

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

回答by josefx

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)

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

回答by Mooing Duck

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.

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

and a function to throw an C++ exception if a Java exception is already in place:

以及在 Java 异常已经存在时抛出 C++ 异常的函数:

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:

我们还有一个 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
    }
};

We also need a function to swallow C++ exceptions and replace them with Java exceptions

我们还需要一个函数来吞下 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");
    }
}

With the above functions, it's easy to use Java/C++ hybrid exceptions in your C++ code, as shown below.

有了上面的函数,就很容易在你的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);
    } 

}

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 NewJavaExceptionto make a copy of that stack, and pass it to setStackTrace.

如果您真的很有抱负,则可以跟踪StackTraceElement[]更大的函数,并获得部分堆栈跟踪。基本方法是给每个函数一个StackTraceElement,并在调用它们时将指向它们的指针推送到线程本地“调用堆栈”,当它们返回时,将指针弹出。然后,更改 的构造函数NewJavaException以制作该堆栈的副本,​​并将其传递给setStackTrace