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のプロジェクトに追加
このステップで以下のようなファイル構成にしていきます。
追加したファイル(フォルダ)は以下です。
- cpp/include
- 先ほどlibsodiumのビルド結果のincludeフォルダをコピーしてきたもの
- cpp/jniLibs/{abi}
- libsodiumのビルド結果のlibフォルダをコピーしてきたもの(.soファイル)
abiというのは特定のアーキテクチャでどのようにソフトウェア同士がやり取りするかの決まりごとのようなものです。
{abi}
の部分にはx86_64
やarm64-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