C/C++のコードをAndroidで動かす(dart:ffiの紹介)

## この記事について
 この記事はKCS Advent Calender 2024の17日目の記事です。
 ↑前
 [↓後](https://tanitanisonson.hatenablog.com/entry/2024/12/18/164452)
 きんべんと申します。今回は、dart:ffiというライブラリを用いてアプリ開発をしたので、それについて備忘録のようなものを書いていこうと思います。といっても、dartには丁寧なチュートリアルが存在していて、dart:ffiにももちろん存在しています。この記事を読んでさらに詳しく知りたくなった方は、こちらから始めるのが一番良いでしょう。

https://dart.dev/interop/c-interop

 また、コード名の最後に`_quote`と記述されているコードや、「サンプルコードからの引用」と記されているコードは、全て以下のhello_worldディレクトリ以下から引用しています。他にもdart:ffiライブラリのサンプルコードが存在しているので是非見てみてください。

https://github.com/dart-lang/samples/tree/main/ffi

 ## dart:ffiの簡単な説明
 dart:ffiはC言語のネイティブAPIを利用するためのライブラリです。ですが、ただライブラリをインストールするだけではCを利用可能にはならず、CMakeを利用する必要があります。例えば、次のようなコードがあったとします。
 ```c:add.c
 int add(int a, int b){
     return a + b;
 }
 ```
 これを、CMakeを使うことでそれぞれの環境に合わせた動的リンクライブラリへと変換します。そして、変換後のファイルをdartからロードする、といった具合です。以下は動的リンクライブラリをロードするまでのコードの例です。pathのjoinメソッドに関しての説明は省きますが、ここでは各プラットフォームに対応するリンクライブラリへのパスを渡していると考えてくれればよいです。
 以下はサンプルコードのライブラリをdart側で読み込む部分です。
 ```Dart:hello_quote.dart
 void main() {
  // Open the dynamic library
  var libraryPath =
      path.join(Directory.current.path, 'hello_library', 'libhello.so');

  if (Platform.isMacOS) {
    libraryPath =
        path.join(Directory.current.path, 'hello_library', 'libhello.dylib');
  }

  if (Platform.isWindows) {
    libraryPath = path.join(
        Directory.current.path, 'hello_library', 'Debug', 'hello.dll');
  }

  final dylib = ffi.DynamicLibrary.open(libraryPath); /*process continues...*/
}
 ```
 サンプルコードでは、それぞれのプラットフォームに合わせた動的共有ライブラリのパスを取ってきて、その位置のライブラリを`ffi.DynamicLibrary.open`で読み込んでいます。
 これで、Cの関数をDart側で呼び出すことができますが、実際には何個か問題があります。まず、呼び出される側のCの関数ですが、ヘッダーファイルでどのような関数が存在するのか示す必要があります。`add.c`に対応する形であれば、次のように書けばよいでしょう。
 ```C:add.h
 int add(int a, int b);
 ```
 また、dart:ffiはC言語のAPIのみを利用可能にするライブラリです。そのため、C++のファイルはそのままだと利用することができません。その場合は、このヘッダーファイルを次のように変更しましょう。
 ```C:add.h
 extern "C" int add(int a, int b);
 ```
 これで、C++の関数も読み出せるようになります。
 もう一つある問題は、共有ライブラリを作ればいいのはわかったけど、そのためにCMakeLists.txtに書くべき内容が分からないということです。分かる方には何の問題がないですが、おそらくFlutterを用いて開発する人の多くがCMakeに関する知識を持っていないと思います。サンプルコードのCMakeLists.txtを見てみましょう。
 ```cmake:CMakeLists.txt
 cmake_minimum_required(VERSION 3.7 FATAL_ERROR)
project(hello_library VERSION 1.0.0 LANGUAGES C)
add_library(hello_library SHARED hello.c hello.def)
add_executable(hello_test hello.c)

set_target_properties(hello_library PROPERTIES
    PUBLIC_HEADER hello.h
    VERSION ${PROJECT_VERSION}
    SOVERSION 1
    OUTPUT_NAME "hello"
    XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Hex_Identity_ID_Goes_Here"
)
 ```
 ここで、hello.defファイルはWindowsの.dllファイルを作成するときに必要なファイルです。このCMakeLists.txtが行おうとしているのは、「`hello_library`をこの`helllo.h`という枠組みで使おうと思うから、`hello`という名前のライブラリにして出力してね」くらいの感覚です。最後の関数に多くのプロパティがついていますが、これらはほとんどWindowsやiOSのためのもので、Androidのみを開発対象とするなら無視してもよいです。基本的には、このテキストの`hello.c`や`hello.h`の部分を自分の用いたいコードの名前にすればよいでしょう。このCMakeLists.txtさえできてしまえば、あとはテキストファイルが存在するディレクトリにて、`cmake .`→`make`と打てばそれぞれの環境に合わせた共有ライブラリが取得できます。
 
 ## Android特有のやり方
 ここまで、簡単な関数の例で動的共有ライブラリの取得、その読み取り方を見てきました。ですが、Androidに必要な.soを上のCMakeLists.txtで取得できるのでしょうか？もちろん、Linuxであれば取得可能ですが、それ以外のOSでは取得できないでしょう。実は、開発対象がAndroidの場合には毎回のコマンドを短縮できるような方法があります。それが、`android\app\build.gradle`を編集して、アプリのビルド時にCMakeも行ってもらう方法です。やり方は単純で、まず、用いたいCのコードをプロジェクトの任意の場所に配置しましょう。おすすめはlibディレクトリ以下に新しいディレクトリを作成して、そこに必要なコードをすべて配置することです。次に、それらのファイルをビルドするためのCMakeLists.txtを`android\app\`に配置しましょう。そして、build.gradleの該当部分に次を記述します。
 ```groovy:android\app\build.gradle
 externalNativeBuild{
    cmake{
        path "CMakeLists.txt"
    }
}
    
 defaultConfig {
    /*...*/
    externalNativeBuild{
        cmake{
            cppFlags '-frtti -fexceptions -std=c++17'
            arguments "-DANDROID_STL=c++_shared"
        }
    }
    /*...*/
}
 ```
 これで、Android上でCを動かす準備が整いました。デバッグを実行すると、自動的に動的リンクライブラリが取得でき、dartのコード側でそれらを読みだすことができます。私は知識不足でわかりませんが、他のOSにも同じように、アプリのビルド時に共有ライブラリを取得する機能はあるかもしれません。

 ## まとめ
 以上、dart:ffiについて、公式のチュートリアルとほとんど内容は同じですが振り返ってみました。おそらく、dart:ffiを使うことはそこまで難しいことではないと思います。ただ、dart:ffiを使えるようになってもUIと処理機構の関連付けは課題として残っているので、今度はそこの解決が求められるでしょう。また、DartのPointerクラスとC言語のポインターは少し異なる部分があったり、C++で定義したクラスはどうやって渡すかなどの課題がまだありますので、ここまで書いたことだけではまだAndroidで動かせるような段階ではないかもしれませんね。ですが、少なくともC言語の共有ライブラリをAndroidで動かす最低限の方法はカバーできたと思います。
 長くなりましたが、ここまでお読みいただきありがとうございました。他のKCSアドベントカレンダーの投稿もぜひ一読ください。