ラッパークラス開発で学ぶ実践C++14  ～JNI～

Android JNI の学習と、C++11/14 [^1]の学習を兼ねて、また、github に好みのもの[^3]がなかったということもあって、(いまさら) JNI ラッパーを自作した。


* **[uc-jni ](https://github.com/uctakeoff/uc-jni)** (github)

[^1]: C++14をターゲットとしたのは、Android Studio が標準で対応していたからだ。
[^3]: あまり処理を持たず、薄い方が良い。ユーティリティ的な処理はあると良いが、使う・使わないを選べたほうがいい。処理がないのだから、コンパイルなんてさせないでほしい。コンパクトであって欲しい。全体を把握しないと使い始められないというのも困る。自分が使う部分だけでも覚えたらすぐ使えて、使いながら徐々に覚えていけるようなものだとうれしい。

これを題材にしてC++の実践方法を記事にしようと思ったが、前座のつもりで書いたライブラリ紹介が思いの外長かったため、実践の話は別の機会に預け、今回は紹介のみとした。

# 概要

`uc-jni.hpp` は**ヘッダファイル一つ**で**コンパイル不要**。インクルードするだけで即座に使える。
ユーティリティ関数もあるが、**コードの大部分を型の定義と解決**に費やしている。

元のJNI関数を使う場合と比べて、余計な実行時コストがなく、簡潔、型安全でリークしにくいコードが書ける(はずだ)。

# メソッドやフィールドへのアクセス

## スタティックメソッド呼び出し

JNIでまず鬱陶しいのが、[タイプシグネチャ](https://docs.oracle.com/javase/jp/1.5.0/guide/jni/spec/types.html#wp16432) と[型ごと定義された関数群](https://docs.oracle.com/javase/jp/1.5.0/guide/jni/spec/functions.html#wp4256)だ。

`uc-jni.hpp` を使うとスタティックメソッドの呼び出しは以下のように書ける。

```cpp
// 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()` 型で定義されるため、**引数や戻り値に値を指定すればコンパイルエラー**となる。

```cpp
// コンパイルエラー
gc(10);

// コンパイルエラー
int x = gc();
```

これだけ情報を詰め込んでも余計なリソースは一切消費しない。**`sizeof(gc) == sizeof(jmethodID)`** である。

## インスタンスメソッド呼び出し

インスタンスメソッドの場合は、以下のように**対象インスタンスを第1引数に渡す必要がある**。

```cpp
// 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,>`](https://cpprefjp.github.io/reference/memory/unique_ptr.html) の別名**だ。
スコープが切れた時点で [`DeleteLocalRef()`](https://docs.oracle.com/javase/jp/1.5.0/guide/jni/spec/functions.html#wp18654) が自動的に呼び出される。

もし、戻り値の `jstring` を操作するのが面倒なら、代わりに `std::string` を返すようにすることもできる。

```cpp
                                              //  ↓ 関数の型を std::string() とする。
auto toString = uc::jni::make_method<jobject, std::string()>("toString"); 
```
これは、**呼び出しごとに`std::string`変換処理が入る**。
そのコストが気になるようなら`jstring()`型を選択すればよい。

同様に `jboolean`を`bool`に変換することもできる。


## コンストラクタ呼び出し・フィールドへのアクセス

メソッドと同様のため、サンプルコードのみ紹介しておく。

```cpp
// 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` の相互変換をサポートする。

```cpp
auto jstr = uc::jni::to_jstring("Hello World!");

std::string str = uc::jni::to_string(jstr); 
```
C++11から導入された[UTF-16を扱う型](https://cpprefjp.github.io/lang/cpp11/char16_32.html)もサポートする。こちらの方がJavaとの相性は良い。

```cpp
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` を好きなだけ放り込んで結合できる。

```cpp
jstring getMessage(jstring name)
{
    return uc::jni::join(u"私の名前は", name, u"です。").release(); 
}
```

# 配列処理

同様に、`jarray` 型と `std::vector` の相互変換もサポートする。

```cpp
// 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);
```

```cpp
// 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>` を使う。[^2]

[^2]: これは、タイプシグネチャを自動解決するために必須の情報だ。 `jobjectArray` を指定すると、シグネチャが  **[Ljava/lang/Object;** になってしまう。

```cpp
// String[] Hoge.intsToString(int[]) メソッドを取得する。
auto intsToStrings 
    = uc::jni::make_method<Hoge, uc::jni::array<jstring>(jintArray)>("intsToStrings");

```

なお、以下のように書いて `std::vector` でやり取りすることもできる。

```cpp
auto intsToStrings 
    = uc::jni::make_method<Hoge, std::vector<std::string>(std::vector<int>)>("intsToStrings");
```


## 低コストAPI

何度も言うように、変換処理はコストがかかるため、低コストなAPIも用意している。

```cpp:Get/SetArrayRegion()の薄いラッパー
// 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());
```

```cpp:GetArrayElements()の薄いラッパー
for (auto&& v : uc::jni::get_elements(array)) {
    // 要素の読み書きOK
}

for (auto& v : uc::jni::get_const_elements(array)) {
    // 要素は読み取り専用
}
```

---

紹介はこのくらいで。興味があれば [ github ](https://github.com/uctakeoff/uc-jni) も覗いてみてほしい。
次は解説記事を書いてみる予定。

<br>
---