dart:ffi をしこたま触る必要があったのでまとめてみました
本記事では分かりやすさを優先した方法にしています
公式な実装の仕方は Flutter のドキュメントをご参照ください (Binding to native code using dart:ffi)
dart:ffi とは
- Dart foreign function interface の略です
- 他の言語で実装された関数(function)を呼び出す事が出来ます
- 現在では実質C言語とC++のみですが、C言語を間に挟む事で利用可能な言語もあります
- 具体的にはダイナミックリンクライブラリ (拡張子 .dll .so .dylib のファイル)を読み込み、その中の関数を実行します
- Flutter の MethodChannel と出来る事がやや近いです
- MethodChannel より手間がかかりますが、状況によってはメリットもあります
メリット
- MethodChannel と違い
async
await
が不要です - 単体テスト(
flutter test
)も出来ます- Flutter2でデスクトップアプリがサポートされたので、将来的には MethodChannel でも実行出来る様になると思われます
- FlutterではなくDart単体で動作します。なのでサーバーサイドDartでもそのままコードを利用出来ます
デメリット
- C言語のポインタをある程度理解する必要があります
- コンパイルをして、各プラットフォームごとにダイナミックリンクライブラリを作成する必要があります
実装
単純に2つの整数を足して返すだけの sum
関数を作って呼び出してみます。
前述の通り、各プラットフォームごとにダイナミックリンクライブラリを作成する必要があります
OS | 拡張子 |
---|---|
iOS | .framework``.dylib |
macOS | .framework``.dylib |
Android | .so |
Windows | .dll |
Dart (Flutter)
import 'dart:ffi';
import 'dart:io';
int sum(int num1, int num2) {
var libraryPath = "";
if (Platform.isIOS)
libraryPath = "sum.framework/sum";
else if (Platform.isMacOS)
libraryPath = "sum.framework/sum";
else if (Platform.isAndroid)
libraryPath = "libsum.so";
else if (Platform.isWindows) libraryPath = "sum.dll";
if (libraryPath.isEmpty) {
assert(false, "Dynamic library is not compatible with this platform.");
return 0;
}
final dylib = DynamicLibrary.open(libraryPath);
// 呼び出す関数を検索してポインタを取得
final pointer =
dylib.lookup<NativeFunction<Int32 Function(Int32, Int32)>>('sum');
// ポインタを Dart で呼び出せる関数として定義
final sum = pointer.asFunction<int Function(int, int)>();
return sum(num1, num2);
}
上記のコードは以下の流れとなっています
- ダイナミックリンクライブラリを開く1
- sum関数を検索してポインタとして取得
- sum関数を Dart で呼び出せる関数に変換
- sum関数の呼び出し
後は各プラットフォームごとにダイナミックリンクライブラリを作成していきます
ライブラリ作成
C言語(プラットフォーム共通)
extern int sum(int, int);
#include "sum.h"
int sum(int num1, int num2) {
return num1 + num2;
}
iOS, macOS
sum.framework
を、iOS用とmacOS用でそれぞれ作成します
Xcode で Framework プロジェクトを作成します。
以下の画像は iOS のものですが、macOS用の場合は上のタブを macOS に切り替えて Framework を選択してください
先ほどの sum.h
と sum.c
をプロジェクトに追加してビルドします
sum.framework
(上記の Products
にある物) を、
利用したい Flutter プロジェクトの ios ディレクトリまたは macos ディレクトリ以下にコピーしてください
Flutter プロジェクトの Runner.xcworkspace
の Build Phases
にある Link Binary With Libraries
と Embed Frameworks
(macOS の場合は Bundle Framework
) に sum.framework
を追加します
iOSでの注意点としては、一つのsum.framework
は実機かシミュレーターのどちらかでしか動作しません
sum.framework
のビルド時に Product
→Destination
→Any iOS Device
をターゲットにすると実機でのみ動作しますし、
iOS Simulators
のどれかをターゲットにするとシミュレーターでのみ動作するライブラリになります
実機とシミュレーター両方で動作させるには XCFramework を作成する必要があります。
参考: https://qiita.com/Arime/items/ee9a41d849b473181728
flutter test
を macOS で動作させたい場合は sum.framework
を
プロジェクトの一番上(flutter
コマンドを実行する階層)にも配置します
Xcodeに追加したsum.framework
(macos
ディレクトリ以下に配置した物)を使いたい場合は、sum.dart
のlibraryPath
を、テスト時のみmacos/sum.framework/sum
となる様な形に処理を加えてください
Android
公式ではndk-build
スクリプトやcmake
コマンドで作る方法(参考)が紹介されていますが、
今回は Android Studio で作ります
Native C++
プロジェクトを作成します
app/src/main/cpp
ディレクトリに移動し、native-lib.cpp
を削除して代わりに sum.c
と sum.h
を配置します
CMakeLists.txt
を以下の内容に書き換えます
cmake_minimum_required(VERSION 3.10.2)
project("sum")
add_library(sum SHARED sum.h sum.c)
app/build.gradle
の com.android.application
を com.android.library
に変更し、applicationId
の行を削除します
plugins {
id 'com.android.application' // id 'com.android.library' に変更
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.example.sum" // ←削除
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
// 以下略
この状態で Build
→Make Project
をすると、
app/build/outputs/aar
ディレクトリの中にapp-debug.aar
というファイルが作成されます
拡張子は.aar
ですが、実際はただのzipファイルなので解凍ソフトで開けます。
開けない場合は拡張子を.zip
にしてください。
また、何故かmacOSの標準アプリだと開けないので自分は The Unarchiver で開きました
ズラッとディレクトリが並んでいます
中にあるjni
フォルダをjniLibs
という名前に変えて、Flutterプロジェクトの
android/app/src/main
に配置します
これで Android でも動作する様になります
ちなみにNative C++
プロジェクトを作成した際に自動で作られるJavaやKotlinのファイルは不要なので、気になる場合は以下の手順で削除も出来ます
-
app/src/main/java
、app/src/main/res
、app/src/test
、app/src/androidTest
ディレクトリを削除 -
app/src/main/AndroidManifest.xml
から<application>〜</application>
の部分を削除 -
build.gradle
(appディレクトリでは無い方)から、テスト関連の行を削除
(test
で検索して、ヒットした行を全て削除すればOKです)
Windows
公式ドキュメントにはまだWindows版に関しての記載はない為
あくまで私的に動作を確認したものとなっております。
実使用の際にはご留意の上でお願いいたします。
Visual Studio 2019 をインストールします。無料のコミュニティ版で構いません。
インストール時にオプションで「C++ CMake ツール」を追加する様にします。
https://visualstudio.microsoft.com/ja/downloads/
次に、cmake でビルドをする為の準備をします。
Android と同じ様に、CMakeLists.txt
とsum.c
、sum.h
を同じフォルダに配置します
加えて、sum.defを新しく作成します
EXPORTS
sum
CMakeLists.txtを以下の内容にします。
Androidとほぼ同じ内容ですが、sum.defが追加されています
cmake_minimum_required(VERSION 3.10.2)
project("sum")
add_library(sum SHARED sum.c sum.h sum.def)
スタートメニューよりx64 Native Tools Command Prompt for VS 2019
を起動します
先ほどの CMakeLists.txt などがあるディレクトリまで移動した後、次のコマンドを実行します
mkdir build
cd build
cmake ..
成功すると、buildフォルダの中に Visual Studio のプロジェクトが作成されますので、「sum.sln
」を開きます
ビルドターゲットを「Release」にして、ビルド
→ソリューションのビルド
を実行します
ビルドが成功すると、Releaseフォルダの中にsum.dll
が作られています
flutter build windows
で動作させたい場合は、生成されたexe
ファイルと同じ場所(build¥windows¥runner¥Release
)にコピーします
flutter run -d window
やflutter test
で用いたい場合は、
sum.dll
をプロジェクトの一番上(flutter
コマンドを実行する階層)に配置します。
先ほどの Dart のlibraryPath
を任意のパスに変更すれば他のフォルダでも構いませんが、flutter build windows
でも反映されてしまう為、リリースビルド時とで処理の変更をおすすめします
これで Windows でも動作する様になります
なお、今回の方法では 64bit 用のDLLを作成出来ます。
32bit の Windows で動作をさせたい場合は
x86 Native Tools Command Prompt for VS 2019
を使用します
備考
本記事は以下の環境で検証しました
Flutter 2.0.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision c5a4b4029c (4 days ago) • 2021-03-04 09:47:48 -0800
Engine • revision 40441def69
Tools • Dart 2.12.0
Android Studio 4.1.1
Build #AI-201.8743.12.41.6953283, built on November 5, 2020
Runtime version: 1.8.0_242-release-1644-b3-6915495 x86_64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
macOS 10.15.7
GC: ParNew, ConcurrentMarkSweep
Memory: 1237M
Cores: 8
Registry: ide.new.welcome.screen.force=true, external.system.auto.import.disabled=true
Non-Bundled Plugins: Dart, io.flutter
cmake version 3.18.1
NDK version 23.0.7123448 rc1
Xcode Version 12.4 (12D4e)
Microsoft Visual Studio Community 2019
Version 16.8.5
VisualStudio.16.Release/16.8.5+31005.135
Microsoft .NET Framework
Version 4.8.04084
インストールされているバージョン:Community
Visual C++ 2019 00435-60000-00000-AA087
Microsoft Visual C++ 2019
Visual Studio Tools for CMake 1.0
cmake version 3.18.20081302-MSVC_2
編集履歴
2021/03/09 Flutter 2.0.1 で再検証
デスクトップアプリに対応
2021/03/03 公式ドキュメントへのリンクと`DynamicLibrary.open`に関する注釈を追加
2021/02/27 初公開
-
iOSに対してはFlutterのドキュメントでは、
DynamicLibrary.open
ではなくDynamicLibrary.process
またはDynamicLibrary.execute
の使用を推奨しています。理由としてはAppleの審査が通るか不明だからとの事ですが、実際にDynamicLibrary.open
を用いたアプリを申請し、問題なく通った為こちらの方法を記述しています。 ↩