Что происходит, когда я бросаю исключение С++ из собственного метода Java?
Предположим, что я встраиваю Sun JVM в приложение на С++. Через JNI я вызываю метод Java (мой собственный), который по очереди вызывает собственный метод, который я реализовал в общей библиотеке.
Что произойдет, если этот собственный метод выдает исключение С++?
edit: компилятор gcc 3.4.x, jvm is sun 1.6.20.
Ответы
Ответ 1
В литературе JNI исключение слова используется исключительно для обозначения исключений Java. Неожиданные события в собственном коде называются ошибками программирования. JNI явно не требует JVM для проверки ошибок программирования. Если возникает ошибка программирования, поведение undefined. Различные JVM могут вести себя по-разному.
Собственная ответственность за перевод всех ошибок программирования в коды возврата или исключения Java. Исключения Java не получают сразу же из собственного кода. Они могут быть отложены, только после того, как исходный код вернется к Java-вызову. Нативный код может проверять ожидающие исключения с помощью ExceptionOccurred
и очистить их с помощью ExceptionClear
.
Ответ 2
Компилятор Java не понимает исключения С++, поэтому вам придется работать как с исключениями Java, так и с С++. К счастью, это не слишком сложно. Во-первых, мы имеем исключение С++, которое сообщает нам, произошло ли исключение 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 уже существует:
inline void assert_no_exception(JNIEnv * env) {
if (env->ExceptionCheck()==JNI_TRUE)
throw ThrownJavaException("assert_no_exception");
}
у нас также есть исключение С++ для бросания новых исключений 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
}
};
Нам также нужна функция для проглатывания исключений С++ и замены их на исключения 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");
}
}
С помощью вышеперечисленных функций легко использовать гибридные исключения Java/С++ в коде С++, как показано ниже.
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[]
ваших больших функций и получать частичную стеклу. Основной метод состоит в том, чтобы дать каждой функции a StackTraceElement
, и, как они называются, надавите на них указатель на локальный "стоп-стол" с потоком, и когда они вернутся, выньте указатель. Затем измените конструктор NewJavaException
, чтобы сделать копию этого стека, и передайте его setStackTrace
.
Ответ 3
Я предполагаю, что ваш JVM сработает. Исходные исключения С++ не распространяются на Java через JNI. Одной из причин этого является то, что JNI является интерфейсом C, а C ничего не знает о С++ исключениях.
Что вам нужно сделать, так это перехватить исключения С++, прежде чем вы войдете в С-код вашего JNI-кода и сделаете функцию JNI C возвратом кода ошибки. Затем вы можете проверить код ошибки внутри Java и при необходимости выбросить исключение Java.
Ответ 4
Я бы назвал это как поведение undefined. Распространение исключений обратно на C-код (то, что работает JVM) - это поведение undefined.
В Windows компиляторы должны использовать Microsoft Structured Exception Handling для реализации исключений, поэтому исключения С++ будут "безопасно" кариться с помощью кода C. Однако, этот код C не написан с учетом исключений, поэтому вы получите крах, если вам повезет, и непоследовательные утечки состояния и ресурсов, если вы не.
На других платформах, ну, я не знаю, но это не может быть красивее. Когда я пишу код JNI, я обертываю каждую функцию С++ в блок try
: даже если я не throw
, я все же могу получить некоторые из стандартных исключений (std::bad_alloc
приходит на ум, но другие возможны тоже).
Ответ 5
JNI использует c-функции для взаимодействия с собственным кодом. C не может правильно обрабатывать исключения, так как он не знает об их существовании. Таким образом, вы должны поймать исключения в своем исходном коде и преобразовать их в исключения Java или ваш jvm сработает. (Это работает, так как исключение java генерируется только после возвращения собственного кода в java)