C++11で追加されたVariadic TemplatesとRvalue Referencesを使って、Android開発時のJNI呼び出しを簡単にする
お久しぶりです。
kaoruです。
前回の投稿からかなり時間が空いてしまいましたが、
みなさんお元気でしょうか。
今回は、C++11で追加されたVariadic TemplatesとRvalue Referencesを使って、Android開発時などに発生するJNI呼び出しを簡単にする方法をご紹介したいと思います。
ただし言語の機能の紹介は他にお譲りし、実際の活用例をご紹介したいと思います。
Androidの開発には一般的にJavaを利用しますが、ネイティブライブラリーの利用、他のプラットフォームとのソースコード共通化、処理の高速化などを目的としてAndroid NDKを利用してC/C++で開発することもあると思います。
このときに手間が多く不具合を発生させやすいのが、JNIによるJava部分とC/C++部分とのやりとりです。
たとえば、Javaで書かれた次のようなクラスを
public class Sample { public Sample(String arg) { } }
JNI経由でC/C++インスタンス化(Javaのコードで書けば new Sample(“string”) というような処理)をしようとすると、以下のようにたくさんのコードを書く必要があります。
const char *str = "string"; jstring str_j = env->NewStringUTF(str); jobject obj_j = env->NewObjectA(clazz, constructor_mid, str_j); env->DeleteLocalRef(str_j); env->DeleteLocalRef(obj_j);
文字列などのJava <-> C/C++ 相互の変換、確保したオブジェクトの解放などお約束が多いためにコードも長くなってしまいますし、ミスや漏れによる不具合も発生しやすくなっています。
C++にある強力な言語機能を使ってお約束はライブラリー側で自動的に処理をするようにして、本質的なことだけに意識を集中できるようにしたいものです。
今回取り上げるひとつはVariadic Templatesと呼ばれる機能で、任意の個数のパラメータの値の型と値を同時に受け取り、それを集合として扱うことができます。今回は、その一部の機能だけを使います。
template <typename... Args> inline void f(Args&&... args) { // 数の取得 int n = sizeof...(args); // 別の関数に渡す場合 g(args...); // 値を加工して別の関数に渡す場合 g(h(args)...); // 初期化子リストに展開 std::array<value_t, sizeof...(args)> x = { h(args)...) }; }
など、かなり柔軟な展開ができます。
次に紹介する機能はRvalue Referencesという機能で、その中でも値と型をそのまま別の関数に転送できるPerfect Forwardingという機能を利用します。
template <typename Arg> value_t g(Arg&& arg) { return h(std::forward<Arg>(arg)) }
このような書き方で、g()はh()に引数の値と型を保持したまま処理を転送することができます。
これらの言語機能を組み合わせると、以下のような方法でJNIの呼び出しを簡単にするライブラリーを作ることができます。
(説明のためのコードであり完全なものではありません。)
template <typename Arg> jvalue jni_param(JNIEnv *env, Arg arg); template <> inline jvalue jni_param(JNIEnv *env, jint arg) { jvalue ret; ret.i = arg; return ret; } template <> inline jvalue jni_param(JNIEnv *env, jlong arg) { jvalue ret; ret.j = arg; return ret; } inline jvalue jni_param(JNIEnv *env, const char *arg) { jvalue ret; ret.l = env->NewStringUTF(arg); return ret; } inline jvalue jni_param(JNIEnv *env, char *arg) { jvalue ret; ret.l = env->NewStringUTF(arg); return ret; } inline jvalue jni_param(JNIEnv *env, std::string const& arg) { jvalue ret; ret.l = env->NewStringUTF(arg.c_str()); return ret; } // ... template <typename... Args> inline local_ref<jobject> create_object(JNIEnv *env, jclass clazz, jmethodID constructor_mid, Args&&... args) { std::array<jvalue, sizeof...(args)> arr = { jni_param(env, std::forward<Args>(args))... }; env->PushLocalFrame(sizeof...(args)); jobject obj_j = env->NewObjectA(clazz, constructor_mid, arr.data()); env->PopLocalFrame(obj_j); return local_ref<jobject>(env, obj_j); } template <typename... Args> inline local_ref<jobject> create_object(JNIEnv *env, local_ref<jclass>& clazz, jmethodID constructor_mid, Args&&... args) { std::array<jvalue, sizeof...(args)> arr = { jni_param(env, std::forward<Args>(args))... }; env->PushLocalFrame(sizeof...(args)); jobject obj_j = env->NewObjectA(clazz.get(), constructor_mid, arr.data()); env->PopLocalFrame(obj_j); return local_ref<jobject>(env, obj_j); } template <typename... Args> inline local_ref<jobject> create_object(JNIEnv *env, local_ref<jclass>&& clazz, jmethodID constructor_mid, Args&&... args) { std::array<jvalue, sizeof...(args)> arr = { jni_param(env, std::forward<Args>(args))... }; env->PushLocalFrame(sizeof...(args)); jobject obj_j = env->NewObjectA(clazz.get(), constructor_mid, arr.data()); env->PopLocalFrame(obj_j); return local_ref<jobject>(env, obj_j); }
このライブラリーを使うと、はじめにあげた new Sample(“string”) というような処理を
auto obj = create_object(clazz, constructor_mid, "string");
と自然な形に書き直すことができ、お約束事項はライブラリー側で自動的に処理してくれます。
このように、Variadic TemplatesとRvalue Referencesは他の言語とのインターフェースを記述する際に強力なツールになりますので、使い方を覚えると便利です。
より実践的な使用例はBoost Libraryなどを見ると随所に現れますので参考になります。
久しぶりの投稿でしたが、いかがだったでしょうか。
それでは、みなさん、毎日寒いですが、風邪などひかれませんように。