💬

AndroidアプリでC++のライブラリを利用する方法

に公開

KotlinのプログラムからC++のライブラリ(or C++のプログラム)を利用する方法について説明します。
C++のプログラムを呼び出す事じたはAndroidのプロジェクトを作成する際にC++のテンプレートを選択するだけでできますが、C++のライブラリを利用する部分でつまづいた部分があったので今回記事を作成しました。
この記事で説明するのはC++のライブラリを直接Kotlinから呼び出すのではなく、C++のライブラリをC++のプログラムを作成してそのC++プログラムをKotlinから呼び出す方法です。
(ライブラリのラッパーを作成するイメージです)

今回のゴール

C++のライブラリであるlibsodiumをAndroidのKotlinのプログラムから利用できるようにする。

Sodium is a modern, easy-to-use software library for encryption, decryption, signatures, password hashing, and more.

上記は公式ドキュメントからの引用ですが、要するに
暗号化や復号を行うライブラリです。

公式ドキュメント

プロジェクトの作成

今回はAndroid StudioのC++を利用するテンプレートを選択してプロジェクトを作成します。
プロジェクトの作成は特に難しい部分はないので、特に説明はしません。

テンプレートの解説

C++のレンプレートを選択してプロジェクトを作成すると、以下のような内容のファイ
ルが生成されていると思います。

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.sampleText.text = stringFromJNI()
    }

    external fun stringFromJNI(): String

    companion object {
        init {
            System.loadLibrary("ndktryout")
        }
    }
}

native-lib.cpp

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

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndktest_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.22.1)
project("ndktryout")
add_library(${CMAKE_PROJECT_NAME} SHARED native-lib.cpp)
target_link_libraries(${CMAKE_PROJECT_NAME} android log)

コードのつながりがそんなに複雑ではないので、簡単に説明します。

  • MainActivity.kt
    • KotlinのプログラムからC++の関数を呼び出すためのコードが書かれています。
    • stringFromJNIという関数を定義して、C++の関数を呼び出しています。
    • System.loadLibrary("ndktest")でC++のライブラリを読み込んでいます。
    • ndktestはCMakeLists.txtで定義したプロジェクト名と同じです。
  • native-lib.cpp
    • C++の関数を定義しています。
    • Java_com_example_ndktest_MainActivity_stringFromJNIという名前の関数を定義しています。
    • この関数はKotlinのstringFromJNIという関数と対応しています。
    • JNIEnvはJNIの環境を表すポインタで、JNIの関数を呼び出すために必要です。
    • jobjectは呼び出し元のオブジェクトを表すポインタです。
    • NewStringUTFはC++の文字列をJNIの文字列に変換する関数です。
  • CMakeLists.txt
    • CMakeの設定ファイルです。
    • cmake_minimum_requiredでCMakeのバージョンを指定しています。
    • projectでプロジェクト名を指定しています。
    • add_libraryでC++のライブラリを定義しています。
    • target_link_librariesでリンクするライブラリを指定しています。

さらに超ざっくりいうと、C++で定義している関数名のJava_以降の部分がどのクラスのどのメソッドとして呼び出すかを示しています。

libsodiumの導入

libsodiumの準備

この部分は本題から少しずれるので詳しくは説明しませんが、libsodiumのソースコードをダウンロードして、libsodiumのディレクトリのdist-buildフォルダ内にある利用したアーキテクチャのスクリプトを実行することでルートディレクトリにビルド結果が格納されているフォルダが出力されます。
ここではx86_64とarm64-v8aの2つのアーキテクチャをビルドするのでこの場合は以下の二つのスクリプトを実行します。

android-x86_64.sh
android-armv8-a.sh

これらのスクリプトを実行するとそれぞれ対応するフォルダの中に以下のものが出力されます。

  • include
    • ヘッダファイル
  • lib
    • ライブラリ本体(今回はこのフォルダの中にある.soファイルを利用します)

ビルドしたものをAndroidのプロジェクトに追加

このステップで以下のようなファイル構成にしていきます。
alt text
追加したファイル(フォルダ)は以下です。

  • cpp/include
    • 先ほどlibsodiumのビルド結果のincludeフォルダをコピーしてきたもの
  • cpp/jniLibs/{abi}
    • libsodiumのビルド結果のlibフォルダをコピーしてきたもの(.soファイル)

abiというのは特定のアーキテクチャでどのようにソフトウェア同士がやり取りするかの決まりごとのようなものです。
{abi} の部分にはx86_64arm64-v8aなどのアーキテクチャ名が入ります。
libsodiumのビルド結果のlibフォルダの中にある.soファイルをabiごとに分けて配置します。

build.gradle.ktsの編集(app)

ビルドして用意したアーキテクチャのabiを指定していきます。
私の場合はx86_64とarm64-v8aのビルドをしたのでこの2つのabiを指定しました。

android {
    // 省略
    defaultConfig {
        // 省略
        ndk {
            abiFilters.add("arm64-v8a")
            abiFilters.add("x86_64")
        }
    }
}

CMakeLists.txtの編集

このステップでは以下のようにCMakeLists.txtを編集します。

cmake_minimum_required(VERSION 3.22.1)
project("ndktryout")
include_directories(${CMAKE_SOURCE_DIR}/include)
add_library(
        sodium
        SHARED
        IMPORTED
)
set_target_properties(
        sodium
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/jniLibs/${ANDROID_ABI}/libsodium.so)
add_library(${CMAKE_PROJECT_NAME} SHARED
        native-lib.cpp
)
target_link_libraries(${CMAKE_PROJECT_NAME}
        sodium
        android
        log)

追加した点

  • include_directories(${CMAKE_SOURCE_DIR}/include)
    • libsodiumのヘッダファイルをインクルードするための設定
  • add_library(sodium SHARED IMPORTED)
    • libsodiumのライブラリをインポートするための設定
  • set_target_properties(sodium PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/jniLibs/${ANDROID_ABI}/libsodium.so)
    • libsodiumのライブラリの場所を指定するための設定
  • target_link_libraries(${CMAKE_PROJECT_NAME} sodium android log)
    • libsodiumのライブラリをリンクするための設定

C++にlibsodumを利用する関数を作成

ここではnative-lib.cppにlibsodiumを利用する関数を作成していきます。

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndktryout_MainActivity_sodiumTest(JNIEnv* env, jobject /* this */) {
    if (sodium_init() < 0) {
        return env->NewStringUTF("Sodium init failed");
    }
    return env->NewStringUTF("Sodium initialized");
}

libsodiumの初期化を行い初期化の結果によって文字列を返す関数を作成しました。
この関数はMainActivity.ktのsodiumTestという関数に対応しています。

C++のプログラムを作成したので、次はKotlinのプログラムから呼び出すようにしていきます。
MainActivityのexternal fun stringFromJNI(): Stringという記述の下にexternal fun sodiumTest(): Stringを追加します。
ここまでできるとKotlin側から呼び出す準備が整いました。
onCreateなどの場所で呼び出してみて戻り値を表示すると先ほどC++で作成した関数が呼び出されていることが確認できると思います。

お疲れ様でした。

Discussion