JNIとC++例外
JNI関連の話が続きます。
今日は短いですが、JNIとC++例外について。
最近ではAndroid NDKもC++の例外(やRTTI)に対応していて、そのおかげで例外が必要なSTLなどのライブラリを自由に使えるようになっています。しかし、Java側はC++の例外を理解してくれないので、もしJNIを介した関数がネイティブ領域で例外をキャッチし損ねると、すぐにSegmentation Faultで落ちます。
解決策としては、JNIから呼び出されたC++側の内部処理を、noexceptな関数に閉じ込めてしまえばよいわけです。もちろんエラーが発見できないのもまずいので、JNIを使ってJava側の例外に変換してしまいます。
JNIEXPORT void JNICALL Java_Foo_bar (JNIEnv* env, jobject thisj) { jni_util::convertException(env, [=] () { // do something with env and thisj // ... throw std::runtime_error("Hoge failed!"); // C++ exception thrown! // ... }); // converts C++ exceptions to Java exceptions }
地味にC++11のラムダキャプチャの恩恵を得られる例でもあります。関数1つを見るとわずかな差ですが、クラスが増えてくると管理も大変になってくるので、記述量を減らす努力は重要です。
実装は以下のように非常に単純。例によってGitHubにも同じものがあります。
https://github.com/meryngii/jni_util
inline void throwRuntimeException(JNIEnv* env, const char* what) { jclass classj = env->FindClass("Ljava/lang/RuntimeException:"); if (classj == nullptr) return; env->ThrowNew(classj, what); env->DeleteLocalRef(classj); } template <typename Func> inline void convertException(JNIEnv* env, Func func) noexcept { try { func(); } catch (std::exception& e) { throwRuntimeException(env, e.what()); } catch (...) { throwRuntimeException(env, "Unindentified exception thrown (not derived from std::exception)"); } }
ここで、Java側の例外が発生しても、C++側はすぐに終了しないという重要な問題があります。JNIの関数を呼び出したときには、常に例外が発生しているかどうかを検査しなければいけません。こんな感じで。
class JavaException : public std::exception { }; // 例外が発生しうるJNIコールの後で呼ぶ inline void checkException(JNIEnv* env) { if (env->ExceptionOccurred()) throw JavaException(); }
上と組み合わせると、C++側で例外を利用したコードを書きつつも、Java側の事情にも配慮することができるというわけです。
今回は次回の布石で、次回はJavaから利用できるようC++のクラスをラップする方法について書きます。