Android JNI の学習と、C++11/14 1の学習を兼ねて、また、github に好みのもの2がなかったということもあって、(いまさら) JNI ラッパーを自作した。
- uc-jni (github)
これを題材にしてC++の実践方法を記事にしようと思ったが、前座のつもりで書いたライブラリ紹介が思いの外長かったため、実践の話は別の機会に預け、今回は紹介のみとした。
概要
uc-jni.hpp
はヘッダファイル一つでコンパイル不要。インクルードするだけで即座に使える。
ユーティリティ関数もあるが、コードの大部分を型の定義と解決に費やしている。
元のJNI関数を使う場合と比べて、余計な実行時コストがなく、簡潔、型安全でリークしにくいコードが書ける(はずだ)。
メソッドやフィールドへのアクセス
スタティックメソッド呼び出し
JNIでまず鬱陶しいのが、タイプシグネチャ と型ごと定義された関数群だ。
uc-jni.hpp
を使うとスタティックメソッドの呼び出しは以下のように書ける。
// java.lang.System クラスを、C++コード上では System という型で定義する。
DEFINE_JCLASS_ALIAS(System, java/lang/System);
// void System.gc() メソッドを取得する。
auto gc = uc::jni::make_static_method<System, void()>("gc");
// System.gc() を実行する。
gc();
タイプシグネチャは必要ないし、関数の呼び分けも考えなくていい。
それを解決するために必要な情報は、コードの上2行ですべて得ることができる。
そして、これらはすべてコンパイル時に解決されるため、実行時に余計な処理が増えることもない。
また、gc()
は void()
型で定義されるため、引数や戻り値に値を指定すればコンパイルエラーとなる。
// コンパイルエラー
gc(10);
// コンパイルエラー
int x = gc();
これだけ情報を詰め込んでも余計なリソースは一切消費しない。sizeof(gc) == sizeof(jmethodID)
である。
インスタンスメソッド呼び出し
インスタンスメソッドの場合は、以下のように対象インスタンスを第1引数に渡す必要がある。
// String Object.toString() メソッドを取得する。
auto toString = uc::jni::make_method<jobject, jstring()>("toString");
// String jstr = obj.toString();
auto jstr = toString(obj);
jstr
の型は local_ref<jstring>
というもので、これは std::unique_ptr<jstring,>
の別名だ。
スコープが切れた時点で DeleteLocalRef()
が自動的に呼び出される。
もし、戻り値の jstring
を操作するのが面倒なら、代わりに std::string
を返すようにすることもできる。
// ↓ 関数の型を std::string() とする。
auto toString = uc::jni::make_method<jobject, std::string()>("toString");
これは、呼び出しごとにstd::string
変換処理が入る。
そのコストが気になるようならjstring()
型を選択すればよい。
同様に jboolean
をbool
に変換することもできる。
コンストラクタ呼び出し・フィールドへのアクセス
メソッドと同様のため、サンプルコードのみ紹介しておく。
// android.graphics.Point クラスのエイリアス定義
DEFINE_JCLASS_ALIAS(Point, android/graphics/Point);
// コンストラクタ Point(int x, int y) を取得する。
auto newPoint = uc::jni::make_constructor<Point(int,int)>();
// フィールドを取得する。
auto x = uc::jni::make_field<Point, int>("x");
auto y = uc::jni::make_field<Point, int>("y");
auto p = newPoint(12, 34); // Point p = new Point(12, 14);
int valueX = x.get(p); // int valueX = p.x;
int valueY = y.get(p); // int valueY = p.y;
x.set(p, 100); // p.x = 100;
y.set(p, 200); // p.y = 200;
文字列処理
jstring
と std::string
の相互変換をサポートする。
auto jstr = uc::jni::to_jstring("Hello World!");
std::string str = uc::jni::to_string(jstr);
C++11から導入されたUTF-16を扱う型もサポートする。こちらの方がJavaとの相性は良い。
auto jstr = uc::jni::to_jstring(u"こんにちは、世界!");
std::u16string str = uc::jni::to_u16string(jstr); // UTF8 への変換コストがかからない。
jstring
を作りたいなら uc::jni::join()
が便利だ。
jstring
, const char*
, const char16_t*
, std::string
, std::u16string
を好きなだけ放り込んで結合できる。
jstring getMessage(jstring name)
{
return uc::jni::join(u"私の名前は", name, u"です。").release();
}
配列処理
同様に、jarray
型と std::vector
の相互変換もサポートする。
// jintArray -> std::vector<int>
std::vector<int> intValues = uc::jni::to_vector<int>(intArray);
// std::vector<int> -> local_ref<jintArray>
auto jintValues = uc::jni::to_jarray(intValues);
// jobjectArray -> std::vector<std::string>
auto stringValues = uc::jni::to_vector<std::string>(objArray);
// std::vector<std::string> -> array<jstring> (後述)
auto jstringValues = uc::jni::to_jarray(stringValues);
uc::jni::array<T>
JNI定義の型で、オブジェクトの配列を表す型は jobjectArray
しかない。
これでは型を明確にすることで得られるC++のメリットが活かせない。
uc-jni
では jobjectArray
の代わりに uc::jni::array<T>
を使う。
jobjectArray
とほぼ同様に使えて、かつ要素の型情報を持たせることができる。
先のメソッド定義において、テンプレートに任意のオブジェクト型配列を指定したい場合にも、このuc::jni::array<T>
を使う。3
// String[] Hoge.intsToString(int[]) メソッドを取得する。
auto intsToStrings
= uc::jni::make_method<Hoge, uc::jni::array<jstring>(jintArray)>("intsToStrings");
なお、以下のように書いて std::vector
でやり取りすることもできる。
auto intsToStrings
= uc::jni::make_method<Hoge, std::vector<std::string>(std::vector<int>)>("intsToStrings");
低コストAPI
何度も言うように、変換処理はコストがかかるため、低コストなAPIも用意している。
// jlong でも jdouble でも同じように書ける。指定された型以外の変数を渡すことはできない。
auto array = uc::jni::new_array<jint>(10);
const std::vector<int> values = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
uc::jni::set_region(array, 0, 10, values.data());
std::vector<int> values2(10);
uc::jni::get_region(array, 0, 10, values2.data());
for (auto&& v : uc::jni::get_elements(array)) {
// 要素の読み書きOK
}
for (auto& v : uc::jni::get_const_elements(array)) {
// 要素は読み取り専用
}
紹介はこのくらいで。興味があれば github も覗いてみてほしい。
次は解説記事を書いてみる予定。
---
-
C++14をターゲットとしたのは、Android Studio が標準で対応していたからだ。 ↩
-
あまり処理を持たず、薄い方が良い。ユーティリティ的な処理はあると良いが、使う・使わないを選べたほうがいい。処理がないのだから、コンパイルなんてさせないでほしい。コンパクトであって欲しい。全体を把握しないと使い始められないというのも困る。自分が使う部分だけでも覚えたらすぐ使えて、使いながら徐々に覚えていけるようなものだとうれしい。 ↩
-
これは、タイプシグネチャを自動解決するために必須の情報だ。
jobjectArray
を指定すると、シグネチャが [Ljava/lang/Object; になってしまう。 ↩