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などを見ると随所に現れますので参考になります。

久しぶりの投稿でしたが、いかがだったでしょうか。

それでは、みなさん、毎日寒いですが、風邪などひかれませんように。


Comments are closed.