C++からKotlin関数を呼びたいし、呼ばれたい

Android 界隈では Kotlin がすでに十分実用段階として広まっているので、C++ でゲーム開発をしている我々は C++ から Kotlin の関数を呼びたくなるわけですよ。
今まではネイティヴプラットフォームに対して操作するとき Java を使ってきましたが、あの長ったらしい Runnable[^1] や Null 安全じゃない世界とはできればおさらばしたいわけです


広いインターネットの海を探してみると Kotlin から C++ を呼び出すことについての記事はいくつか見つかるのですが、C++ から Kotlin を呼び出すことについて情報が少なかったので今回調べたことを残しておくことにしました。


# 準備

Android Studio の New Project から `Native C++` のテンプレートを選択して生成されたプロジェクトをベースにやっていきます。

- Android Studio 3.3.2
- NDK 19.2.5345600
- macOS 10.14.4

# C++ → Kotlin 呼び出し

### Kotlin 側コード

C++ から int を引数にとって string を返す Kotlin の static 関数を呼ぶケースを考えます。
Kotlin 側に次のようなファイルを追加します。

```KotlinFunction.kt
package com.example.kotlinjni

class KotlinFunction {
    companion object {
        @JvmStatic
        fun hello(value: Int) = "Hello from Kotlin. value = ${value}"
    }
}
```

Kotlin には static 関数という概念がないので companion object を使うのですが、その際に `@JvmStatic` をつけることが重要みたいでした。これを付与することによって Java インターフェイスからはあたかも今までの static 関数であるかのように振る舞えます。

実際にこの Kotlin コードをバイトコードにしてそれを Java にデコンパイルした結果が次のようになります（冗長な部分を一部省略しています）

```KotlinFunction.decompiled.java
...
public final class KotlinFunction {
   public static final KotlinFunction.Companion Companion = new KotlinFunction.Companion((DefaultConstructorMarker)null);

   @JvmStatic
   @NotNull
   public static final String hello(int value) {
      return Companion.hello(value);
   }

   public static final class Companion {
      @JvmStatic
      @NotNull
      public final String hello(int value) {
         return "Hello from Kotlin. value = " + value;
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
```

`KotlinFunction.hello` の形で呼べそうですね！

### C++ 側コード

続いて C++側 です。次のコードをテンプレートで存在していた `native-lib.cpp` なり好きな場所に配置します。
こちらは既存の JNI 呼び出しの形と一切変化がありません。これまでの Java 用の資産が完全にそのまま使えると思います。さすが100% Java 互換をうたっているだけはあります

```cpp
#include <jni.h>
#include <string>

static JNIEnv* _env;

std::string callStaticStringMethod(const char* className, const char* methodName, int intArg) {
    std::string result {};

    auto clazz = _env->FindClass(className);
    auto methodID = _env->GetStaticMethodID(clazz, methodName, "(I)Ljava/lang/String;");
    if (methodID != nullptr) {
        auto jstr = static_cast<jstring>(_env->CallStaticObjectMethod(clazz, methodID, intArg));
        const char *chars = _env->GetStringUTFChars(jstr, nullptr);
        result = chars;

        _env->ReleaseStringUTFChars(jstr, chars);
        _env->DeleteLocalRef(jstr);
    }

    _env->DeleteLocalRef(clazz);
    return result;
}
```

一般的な JNI コールのお作法をそのまま実行します。(簡単のためにenvを雑に扱ってますが。)
初見は `"(I)Ljava/lang/String;"` のシグネチャなどが意味不明だと思いますが、これは「intを引数にとってstringを返す関数」という意味をしています。詳しくは JNI について調べると書いてあると思います。


```native-lib.cpp
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_kotlinjni_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    _env = env;

    std::string result;
    result = callStaticStringMethod("com/example/kotlinjni/KotlinFunction", "hello", 5);

    return env->NewStringUTF(result.c_str());
}
```

## 結果
<img width=40% alt="スクリーンショット 2019-04-07 16.37.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/121299/eb703193-68ca-4533-623f-ac3fad8bcb33.png">

よさそう。


# Kotlin → C++ 呼び出し

### 自動でC++メソッドを探索してもらうやり方

`Native C++` テンプレートには最初から次のような形で Kotlin → C++ の呼び出しが実装されており、見よう見まねでやればとりあえずできます

```MainActivity.kt
...
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Example of a call to a native method
        sample_text.text = stringFromJNI()
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    external fun stringFromJNI(): String

    companion object {

        // Used to load the 'native-lib' library on application startup.
        init {
            System.loadLibrary("native-lib")
        }
    }
}
```

```native-lib.cpp
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_kotlinjni_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    ...
}
```

Java でいう
 `static native void hogehoge();` 
は Kotlin では
`external fun hogehoge()`
として表現されるように見えます。

### C++メソッドを自分で登録するやり方

このままでも良いのですが、
`extern "C" JNIEXPORT jstring JNICALL
Java_com_example_kotlinjni_MainActivity_stringFromJNI(`
この記述は難しいです。Kotlin 側のパッケージ名やクラス名が変わったときなどに対応が大変そうです。
また `System.loadLibrary("native-lib")` も雰囲気でしかよくわかっていません。

そこで、動的にあとから C++ のメソッドを Kotlin(Java) 側に登録する方法も紹介しておきます

```native-lib.cpp
void registerNative(const char* className, const char* jmethodName, const char* jsignature, void* methodPtr) {
    auto clazz = _env->FindClass(className);

    JNINativeMethod methods[] {
        { jmethodName, jsignature, methodPtr },
    };

    _env->RegisterNatives(clazz, methods, 1);
    _env->DeleteLocalRef(clazz);
}

namespace CppFunction {
    static jstring nativeStringFromJNI(JNIEnv* env) {
        const char* result = "Hello from Cpp";

        return env->NewStringUTF(result);
    }
}

extern "C" JNIEXPORT void JNICALL
Java_com_example_kotlinjni_MainActivity_register(
        JNIEnv* env,
        jobject /* this */) {
    _env = env;
    registerNative("com/example/kotlinjni/CppFunction",
            "stringFromJNI",
            "()Ljava/lang/String;",
            (void*)CppFunction::nativeStringFromJNI);
}
```

`registerNative` 関数で Kotlin 側の関数と C++ 側の関数を紐づけます。この際にもシグネチャが必要になります。
Kotlin側の記述は以下です。

```MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        register() // Cppの関数をKotlinから参照できるように最初にどこかでこれを呼ぶ

        sample_text.text = CppFunction.stringFromJNI()
    }

    external fun register()

    companion object {
        init {
            System.loadLibrary("native-lib")
        }
    }
}
```

```CppFunction.kt
package com.example.kotlinjni

class CppFunction {
    companion object {
        @JvmStatic
        external fun stringFromJNI(): String
    }
}
```

`register` というエントリーポイントを用意し、その中で Kotlin 側の関数に対応する C++ 側の関数を JNIEnv に登録していきます。
個々の関数を一つずつ登録していくのがめんどくさいっちゃめんどくさいです。Kotlin 側で `System.loadLibrary` を呼ぶ必要はないですが、どちらが扱いやすいかは皆さんの判断にお任せします。


## 結果
<img width=40% alt="スクリーンショット 2019-04-07 17.05.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/121299/ea7b1644-e4d0-c192-a9ed-75bf7dbff4f2.png">
よさそう。

# Proguard への記述

C++ から Java コードを呼び出す際はメソッドをシンボルで呼ぶため、proguard の影響をもろにうけます。
シンボルを壊されないよう、proguard を使用する際は以下を記述することを忘れないようにしましょう。

```proguard-rules.pro
-keep class com.example.kotlinjni.** {
    native <methods>;
    public static <methods>;
}
```
# 所感

C++ から見たときに完全に Java と同じ形式で扱えるのでやりやすいなと思いました。
Kotlin 側でも `@JvmStatic` をつけるということさえわかればあとは自然に Kotlin を使用できるので今後はプラットフォーム関数を呼ぶときに積極的に Kotlin を使っていきたいと思います。

iOSのほうはBridging Headerあたりがちょっと難しいのと、Objective-C++ が便利すぎるので　C++ から Swift を使うモチベがあまりないのですが、Kotlin に関しては Java の完全上位互換な印象を受けました。



[^1]: いつからかJavaでもラムダが使えるようになって最近は便利になりましたね