Что происходит, когда я выбрасываю исключение C ++ из нативного метода Java? - PullRequest
22 голосов
/ 09 ноября 2010

Предположим, я встраиваю JVM от Sun в приложение C ++. Через JNI я вызываю метод Java (мой собственный), который, в свою очередь, вызывает собственный метод, который я реализовал в общей библиотеке.

Что произойдет, если этот нативный метод вызовет исключение C ++?

edit: компилятор gcc 3.4.x, jvm для sun 1.6.20.

Ответы [ 5 ]

24 голосов
/ 18 августа 2012

Компилятор 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) {}
};

и функция, которая выдает исключение C ++, если исключение Java уже существует:

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, и, когда она вызывается, поместить указатель на них в локальный поток вызовов и, когда они вернутся, отключить указатель.Затем измените конструктор NewJavaException, чтобы сделать копию этого стека, и передайте его setStackTrace.

5 голосов
/ 10 ноября 2010

В литературе JNI слово исключение , по-видимому, используется исключительно для обозначения исключений Java.Неожиданные события в собственном коде называются ошибки программирования .JNI явно не требует, чтобы JVM проверяли ошибки программирования.Если происходит ошибка программирования, поведение не определено.Различные JVM могут вести себя по-разному.

Собственный код отвечает за перевод всех ошибок программирования в коды возврата или исключения Java.Исключения Java не генерируются немедленно из собственного кода.Они могут быть ожидающими , только выданными, как только нативный код возвращается к вызывающей стороне Java.Собственный код может проверять наличие ожидающих исключений с помощью ExceptionOccurred и очищать их с помощью ExceptionClear.

3 голосов
/ 10 ноября 2010

Я бы назвал это неопределенным поведением. Распространение исключений в коде C (это то, что запускает JVM) - это неопределенное поведение.

В Windows компиляторы должны использовать структурированную обработку исключений Microsoft для реализации исключений, поэтому исключения C ++ будут «безопасно» передаваться через C-код. Однако , этот код на C не написан с учетом исключений , поэтому вы получите сбой, если вам повезет, и несогласованные состояния и утечки ресурсов, если вы этого не сделаете. 1007 *

На других платформах, ну, я не знаю, но это не может быть красивее. Когда я пишу код JNI, я оборачиваю каждую функцию C ++ в блок try: даже если я не throw, я все равно могу получить некоторые стандартные исключения (std::bad_alloc приходит на ум, но возможны и другие) ).

3 голосов
/ 09 ноября 2010

Я предполагаю, что ваша JVM потерпит крах.Собственные исключения C ++ не распространяются в Java через JNI.Одной из причин этого является то, что JNI является интерфейсом C, а C ничего не знает об исключениях C ++.

Что вам нужно сделать, это перехватить исключения C ++, прежде чем вы попадете на уровень C вашего кода JNI, и заставить функцию JNI C возвращать код ошибки.Затем вы можете проверить код ошибки внутри Java и при необходимости выдать исключение Java.

2 голосов
/ 10 ноября 2010

JNI использует функции c для взаимодействия с собственным кодом. C не может правильно обрабатывать исключения, поскольку не знает об их существовании. Поэтому вы должны перехватывать исключения в своем родном коде и преобразовывать их в исключения Java, иначе ваш jvm потерпит крах. (Это работает, поскольку исключение java генерируется только после возврата нативного кода в java)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...