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++のクラスをラップする方法について書きます。
参照カウントとバッファ再利用
std::shared_ptrの参照カウントを、バッファメモリの再利用に応用できるのではないかというお話。例として、単純なリソースクラスとそれのローダを考えてみます。
#include <vector> #include <memory> #include <numeric> class Resource { public: std::size_t size() const { return data_.size(); } void resize(std::size_t size) { data_.resize(size); } std::vector<int>::iterator begin() { return data_.begin(); } std::vector<int>::iterator end() { return data_.end(); } private: std::vector<int> data_; }; class Loader { public: std::shared_ptr<Resource> load(); private: std::shared_ptr<Resource> resource_; }; std::shared_ptr<Resource> Loader::load() { if (!resource_.unique()) // <=== resource_ = std::make_shared<Resource>(); resource_->resize(100); std::iota(resource_->begin(), resource_->end(), 0); // prepares data return resource_; } void f() { Loader loader; // load and release for (int i = 0; i < 100; i++) { auto resource = loader.load(); // "loader" reuses "resource_" // because "resource" is released here. } } void g() { Loader loader; // load and store std::vector< std::shared_ptr<Resource> > gotResources; for (int i = 0; i < 100; i++) { auto resource = loader.load(); gotResources.push_back(resource); // "loader" does not reuse "resource_" // because it's still shared with gotResources, } // ... }
毎回同じデータが返ってくるくだらないコードですが…。
話の肝は<===と書いてあるところの2行だけです。要はshared_ptrの指している先がunique(=参照カウントが1)だったら、自分しか使ってない訳だからリソースを再利用してもいいじゃん、というわけです。
参照カウントが0(=何も持っていない)ときにもuniqueにはならないので、nullの場合も自動的に確保されます。
大きなデータを受け取る場合は、利用側であらかじめ一時バッファ変数を用意しておいて、内部処理でそこに入れてもらうというやり方が一番よく使われますし簡単です。しかし、メモリを再利用したい場合は利用側が適切に管理する必要がありますし、バッファの型も一つに固定されます。上の例のようにshared_ptrで利用者にバッファを返却し、その利用状況を逐一監視しておけば、もう少し柔軟に運用できるんじゃないかというわけです。
shared_ptrの参照カウントを、単なるメモリの確実な解放だけでなく別の面白い用途に使えないかなあと考えたお話でした。
もっとも、上の例は一番単純な例で、もう少しうまくやるにはバッファプールなどを作る必要があると思います。ちなみに、weak_ptrだと解放されてしまうのでうまくいきません。
JNIの自動リソース解放
久々に記事を書こうと思い立ったので書きます。ブログという形態が情報共有に適切なのかどうかはさておいて、小さなひらめきであっても何らかの形で記録していかなければ自分の糧にならない、と改めて感じている今日この頃です。
最近Android NDKで作業する機会を得ていまして、C++をモリモリ書ける喜びを感じています。Androidデバイス本来の力を生かすにはネイティブコードは現状必須です。初期のAndroidのモッサリ感は、限られた資源の中でわざわざJava VM上にアプリケーションを実行しているせいだったのではないかと勘ぐることもしばしばです。
ネイティブコードを利用したAndroidアプリケーションを作るにはJNI (Java Native Interface)を使うことが必須となるのですが、このJNIがいろんな意味で非常に厄介です。JNIの問題点を適当にあげつらってみます。
- グルーコードを書くのが非常に面倒。(アプリケーションの本質でないところに労力をさく必要)
- ネイティブ領域で各種IDをいちいちキャッシュしないと実行速度が大幅に低下する。
- JavaもC++も静的コンパイルされる言語なのに、実行時まで正常に関数が呼び出されるかわからない。(失敗するとUnsatisfiedLinkErrorがthrowされる)
- ローカル参照は512個まで。(DeleteLocalRefで解放せずにそれ以上確保しようとすると即死)
以上のような内容に関して、IBMのサイトの以下の記事が参考になります。多くのこういったバッドノウハウ系の記事は読む気がしないものですが、この記事に関しては隅々まで非常に実践的な内容なのでおすすめです。
http://www.ibm.com/developerworks/jp/java/library/j-jni/
今回は、文字列と配列アクセスのグルーコードを書く手間を削減する方法についてです。大した内容ではないですが、一応ソースコードはGitHubにあげてあります。
https://github.com/meryngii/jni_util
Javaから渡される文字列と配列へのアクセスには、事前にネイティブ領域にロードする関数を呼び出す必要があります。そしてさらに、ネイティブ関数が終了するときには解放処理の関数をまた呼び出す必要があります。こんな感じで。
extern "C" JNIEXPORT void JNICALL Java_Foo_bar (JNIEnv* env, jobject thisj, jintArray arrayj) { jboolean isCopy; int* elements = env->GetIntArrayElements(arrayj, &isCopy); // Do something with array... env->ReleaseIntArrayElements(arrayj, elements, 0); }
C++プログラマとしてはRelease系の関数を直接呼び出したくないもので、これはRAII的にデストラクタで処理させるものでしょう。同じ考えのコードは探してみたらありました。
JNI C++ templates to acquire/release resources
あといちいち要素の型名を書かなければいけないのも気に食わないですね。ここはテンプレートでうまいことやるべきでしょう。というわけで以下のように。
#include "jni_util.hpp" extern "C" JNIEXPORT void JNICALL Java_Foo_bar (JNIEnv* env, jobject thisj, jintArray arrayj) { jni_util::CopiedArrayAccess<jint> array(env, arrayj); int* elements = array.elements(); size_t size = array.size(); // Do something with array... // Destructor automatically releases the resource }
まあ結局jintとか書いてる訳ですが…。本当はちゃんとiteratorをつけてコンテナとしての要件を満たすようにしようかなとも思うのですが、とりあえず実用上はこれで十分でしょう。
JNIでのJava配列へのアクセスには2種類あって、
- Get***ArrayElementsで一気にコピーする
- Get***ArrayRegionで少しずつコピーする
の2種類あります。それぞれ適当にCopied, Regionalと名前をつけて対応させておきました。
Release時の第3引数はmodeで、0, JNI_COMMIT, JNI_ABORTから選べます。0にしてReleaseしないと変更した値がJava側で反映されません。読み出しのみの場合はJNI_ABORTでOKです。*1modeに関してもコンストラクタに与える第3引数として付加してあります。
とりあえずこんなところで。
*1:JNI_COMMITって何のために使うんだろう…。
unite-outlineのLua向けinfo作った
今さらUnite.vim便利そうなので使い始めました。
unite-outlineを追加すると簡易アウトラインが見れるわけですが、Lua用が無かったので作りました。製作時間2時間半。
https://github.com/meryngii/unite-outline/blob/master/autoload/unite/sources/outline/defaults/lua.vim
単純に関数の一覧を生成するだけです。ラムダとかどうしようか…。
VimScriptは初めて書きましたけど、:helpは大事だと思いました。
他にも足りないものがあったら追加しようと思います。
amPlugのAUX端子修理
珍しく電子工作な話です。amPlugという安価で著名なヘッドホンアンプがありまして、半年前にベース用を買ったらどこでも練習できるようになって捗っていたのです。しかし、今日の練習前にAUX端子に刺したケーブルの根元を机にぶつけ、片耳側が鳴らなくなってしました…。
KORGのサイトには無料修理する気がないようにしか見えなかったので、分解修理を決行。入力端子の裏のねじを一本外して、カバーの真ん中をグリグリしていると比較的簡単に外れます。
コネクタを軽く押してみると、丸印のところが基板から剥がれそうになっていました。パターンを読むとたまたま近くに半田が盛ってあったので、リード線でつなげてあげるとあっさり修理完了。
未だに少し接触不良が残っていますが、両耳聞こえるようになりました。
メカニカルな部品はすぐに壊れるので困りますね。僕がパソコンを壊すのはいつもハードディスクか、周辺機器の端子からだったりしますし。
数式のLL構文解析
以前から数式処理をやってみたいと思っていたので、その下準備として構文解析を適当に書いてみました。完成に至るまで二転三転しましたが…。
それにしてもこういうコード片だとHaskellは大活躍ですね。Haskellすごい。
import Data.Char -- 以下から引用。標準のreadは失敗するとエラーを投げる。 -- http://d.hatena.ne.jp/sshi/20060630/p2 read' :: (Read a) => String -> Maybe a read' s = case [x | (x,t) <- reads s, ("","") <- lex t] of [x] -> Just x _ -> Nothing -- 式表現 data Expr = Const Integer | Symbol String | Add Expr Expr | Mul Expr Expr | Power Expr Expr deriving Show showExpr :: Expr -> String showExpr (Const x) = show x showExpr (Symbol x) = x showExpr (Add x y) = "(" ++ showExpr x ++ " + " ++ showExpr y ++ ")" showExpr (Mul x y) = "(" ++ showExpr x ++ " * " ++ showExpr y ++ ")" showExpr (Power x y) = showExpr x ++ "^" ++ showExpr y -- 構文解析 term, power, factor, expression :: [String] -> (Expr, [String]) term ("(":is) = let (e, (")":is')) = expression is in (e, is') term (i:is) = case read' i of Just x -> (Const x, is) Nothing -> (Symbol i, is) power = pow . term where pow (e, "^":is) = let (e', is') = power is in (Power e e', is') pow x = x factor = fac . power where fac (e, "*":is) = let (e', is') = power is in fac ((Mul e e'), is') fac (e, "/":is) = let (e', is') = power is in fac ((Mul e (Power e' (Const $ -1))), is') fac x = x expression = exp . factor where exp (e, "+":is) = let (e', is') = factor is in exp ((Add e e'), is') exp (e, "-":is) = let (e', is') = factor is in exp ((Add e (Mul (Const $ -1) e')), is') exp x = x -- 字句解析 lexer :: String -> [String] lexer [] = [] lexer (x:xs) | isAlpha x = wordWhile (\x -> isAlpha x && isDigit x) | isDigit x = wordWhile isDigit | isSpace x = lexer xs | isAscii x = [x] : lexer xs where wordWhile p = (x : takeWhile p xs) : (lexer $ dropWhile p xs) -- 構文解析 + 字句解析 parse = fst . expression . lexer
実行結果
> parse "a+b+c" Add (Add (Symbol "a") (Symbol "b")) (Symbol "c") > showExpr $ parse "1*2+3/4" "((1 * 2) + (3 * 4^-1))" > showExpr $ parse "a*y^2-b/c*y+1" "(((a * y^2) + (-1 * ((b * c^-1) * y))) + 1)"
減算と除算はそれぞれ乗算と累乗に直してあります。Subとかを作るのは簡単にできます。
LL法のパーサを実装すると、必ず左再帰の問題が発生しますね。
wikipedia:左再帰
今回作ったパーサは、左再帰を回避しつつ、左結合の構文木が作られるように配慮してあります。左結合の演算子(+, *)では、再帰呼び出しの中で、引数に呼び出し元の情報を溜め込んでいくのがポイントです。以下にBNFも置いておきます。
expression = factor expression' expression' = + factor expression' | - factor expression' | ε factor = power factor' factor' = * power factor' | / power factor' | ε power = term power' power' = ^ power' | ε term = Const | Symbol | ( expression )
実行速度は遅いと思いますが、こういうコードで速度が要求される場面もあまりないでしょう。もっといい方法あったら教えてください。
Template Aliases
C++11 Advent Calenderの26日目の記事です。
クリスマスまで残り365日と迫りましたね。C++11 Advent Forever.
さて、C++11で導入された機能の一つがTemplate Aliasesです。元々はTemplate Typedefなどとも呼ばれていました。他の主要な機能と比べて大して目立っていないですが、細かいポイントがいくつかあるような気がします。
Template Aliasesとは?
C言語でもtypedefを使えば型に好きな別名を付けることができます。C++のクラスも型ですからtypedefできます。ではテンプレートクラスはどうかというと、C++03まではそのままの名前で使うしか無かったのです。Template Aliasesならテンプレートに別名を与えることができます。そう、C++11ならね。
template <typename T> using Vec = std::vector<T>; // Template Alias Vec<int> v; // OK: std::vector<int> v; と同じ
あらかじめ引数を当てはめておくこともできます。
// 文字列をキーとする辞書(本当はC++11ならunordered_mapがオススメ) template <typename T> using Dictionary = std::map<std::string, T>; Dictionary<double> dic; // std::map<std::string, double> dic; と同じ
もう一つ例を挙げると、自作のアロケータを簡単に使えるというものがあります。
template <typename T> class MyAllocator { /* ぼくの考えたさいきょうのアロケータ */ }; template <typename T> using MyVector = std::vector<T, MyAllocator<T>>; // アロケータを埋め込む MyVector<int> v; // 指定しなくても上のアロケータを使える
ここまでは割と簡単な例ですが、もっと複雑な型も自在に表現できます。なぜかというとメタプログラミングで作った型を取り出すことができるからです。
#include <type_traits> // constを取り除く std::remove_const<const int>::type x1; // int x1; template <typename T> using remove_const = typename std::remove_const<T>::type; // typenameは必要 remove_const<const int> x2; // int x2;
- typeが要らなくなるだけでも大分格好良くなります。
typedefを置き換える(普通の型のエイリアス)
Template Aliasesはテンプレート引数+エイリアスという形で成り立っているので、エイリアスの部分だけ使用するとtypedefの代わりとして使えます。
typedef int intA_t; // 古いシンタックス(C or C++03) using intB_t = int; // 新しいシンタックス(C++11)
これによって、C言語の邪悪なシンタックス*1から幾分か解放されることになります。
typedef double (*func1A_t)(int); // どれが新しい型名だろう? using func1B_t = double (*)(int); // 新しい型名が分かりやすい using func1C_t = auto (*)(int) -> double; // 戻り値の後置も使ってみる // intを引数に取って、doubleの配列への参照を返す関数へのポインタ typedef double (&(*func2A_t)(int))[100]; // オエーッ using func2B_t = double (&(*)(int))[100]; using func2C_t = auto (*)(int) -> double (&)[100]; // 若干読みやすい?
戻り値の後置については省略*2。配列への参照は昔書いたネタです。
alias declarationによって、typedefはCとの互換性以外の必要性が無くなりました。ナウでヤングなC++11プログラマはusingを使うべきなのです。数十年後typedefは絶滅する運命にあるのです。
変数のエイリアスは作れません
ところでよくある次のようなusing declaration
using std::string;
は、新しく導入されたエイリアスによって
using string = std::string;
とと同じになります。つまり、型のusing declarationはalias declarationの糖衣構文とも考えることができるらしいのです。
しかし、右辺に取れるのは型だけなので、
using std::cout; // OK using Cout = std::cout; // エラー
となって変数では同様の考え方が成り立たないということになります。変数のエイリアスは十分なメリットがないとして導入されていません。
Template Aliasesの特殊化はできません
Template Aliasesは特殊化できません。例えば次のようなコードは不正です:
// 空想のコード template <int> using int_exact; template <> using int_exact<8> = signed char; template <> using int_exact<16> = short; int_exact<16> val; // short val;
しかしクラスの特殊化と組み合わせれば、
// コンパイルできる template <int> struct int_exact_traits; template <> struct int_exact_traits<8> { using type = signed char; }; template <> struct int_exact_traits<16> { using type = short; }; template <int N> using int_exact = typename int_exact_traits<N>::type; // OK int_exact<16> val; // short val; と同じ
このようなコードが書けるので問題ないわけです。Template Aliasesの特殊化が認められていない理由は、クラステンプレートの特殊化・エイリアスの特殊化の2つが入り乱れて混乱することを防ぐためだそうです。
Template Template Parametersに投入する
面白いことに、Template Aliasesはテンプレート実引数にできると標準で定められています。
c++ - Can I use template aliases as template template parameters? - Stack Overflow
これによってTemplate Template Parametersを使う際の問題点が改善されます。
Template Template Parameters - Faith and Brave - C++で遊ぼう
// (型ではなく)テンプレートクラスを引数に取る template <template <typename> class Container> class A { /* ... */ }; template <typename T> using MyVector = std::vector<T, MyAllocator<T>>; // 先ほどの例 A<std::vector> a1; // エラー A<MyVector> a2; // OK!!!
そのままのstd::vectorはテンプレート引数が2つ必要ですから、A
Template Template Parametersの応用というと真っ先にLokiのポリシーが思い浮かびますが、他はパッと思いつかないです。Boostのどこかで使われているかもしれません。
Template Aliasesを継承で置き換えられるか?
Template Aliasesが存在しなかったころにも代替策がいくつかありました。
http://msdn.microsoft.com/ja-jp/library/cc440199%28v=vs.71%29.aspx
- マクロを使ってみる。(ダサい上に問題が多い)
- テンプレートクラスで派生してみる。
- traitsをそのまま利用する。仕方なく::typeを書く。Type Generatorとも言うらしい。
ここで派生に目を向けると、C++11ではコンストラクタを継承できる*3のでより簡単に実現できます。
template <typename T> class MyVector : public std::vector<T> { using Base = std::vector<T>; // ここでもAlias Declaration public: using Base::Base; // コンストラクタの継承 };
いわゆるStrong Typedefと同じで、元の型とは異なる型になります。ただこれだけだと何か足りないかもしれないです。
まとめ
- Template Aliasesによって既存のテンプレートクラスを自在に変形することができます。
- Template Aliasesはテンプレートメタプログラミングのインターフェイスとして活躍するでしょう。
- typedefの代わりにこれからはusingを使いましょう。
- 変数のエイリアスや、Template Aliasesの特殊化は使えません。
- 継承コンストラクタを使えば派生クラスである程度代替ができます。
Template AliasesはGCC 4.7から導入されています。この記事に使用したコンパイラはgcc version 4.7.0 20111217 (experimental)です。
Inheriting Constructorsはまだ実装されていないようです。もうそろそろ実装されると思うのですが…。
参照リンク
まず最初に読むならDr.StroustrupのC++11 FAQが簡潔で分かりやすいです。他の項目も合わせてオススメです。
http://www2.research.att.com/~bs/C++0xFAQ.html#template-alias
詳しい解説は提案に書いてあります。
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1489.pdf
その他色々参考にしました。
GCC-4.7 に Alias declarations (Template aliases) が実装されていた - 野良C++erの雑記帳
Template Aliases すごい - C++でゲームプログラミング
*1:D&E 2.8.1も参照のこと。
*2:C++0x 新たな関数宣言構文 - Faith and Brave - C++で遊ぼうなど。引数に依存した戻り値を書きたいのが元々の動機ですが、スコープを解決できるなど他にも利点があります:http://www2.research.att.com/~bs/C++0xFAQ.html#suffix-return
*3:C++0x 継承コンストラクタ(Inheriting Constructors) - Faith and Brave - C++で遊ぼう