LoginSignup
8
6

More than 3 years have passed since last update.

【Flutter2対応】dart:ffi を Flutter で使ってみる

Last updated at Posted at 2021-02-27

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)

sum.dart
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. ダイナミックリンクライブラリを開く1
  2. sum関数を検索してポインタとして取得
  3. sum関数を Dart で呼び出せる関数に変換
  4. sum関数の呼び出し

後は各プラットフォームごとにダイナミックリンクライブラリを作成していきます

ライブラリ作成

C言語(プラットフォーム共通)

sum.h
extern int sum(int, int);
sum.c
#include "sum.h"

int sum(int num1, int num2) {
    return num1 + num2;
}

iOS, macOS

sum.frameworkを、iOS用とmacOS用でそれぞれ作成します

Xcode で Framework プロジェクトを作成します。
以下の画像は iOS のものですが、macOS用の場合は上のタブを macOS に切り替えて Framework を選択してください
iosmacos-ss0.png

先ほどの sum.hsum.c をプロジェクトに追加してビルドします
iosmacos-ss1.png

sum.framework (上記の Products にある物) を、
利用したい Flutter プロジェクトの ios ディレクトリまたは macos ディレクトリ以下にコピーしてください

Flutter プロジェクトの Runner.xcworkspaceBuild Phases にある Link Binary With LibrariesEmbed Frameworks (macOS の場合は Bundle Framework) に sum.framework を追加します

iosmacos-ss2.png

iOSでの注意点としては、一つのsum.framework は実機かシミュレーターのどちらかでしか動作しません

sum.frameworkのビルド時に ProductDestinationAny iOS Device をターゲットにすると実機でのみ動作しますし、
iOS Simulatorsのどれかをターゲットにするとシミュレーターでのみ動作するライブラリになります

実機とシミュレーター両方で動作させるには XCFramework を作成する必要があります。
参考: https://qiita.com/Arime/items/ee9a41d849b473181728

flutter test を macOS で動作させたい場合は sum.framework
プロジェクトの一番上(flutterコマンドを実行する階層)にも配置します

Xcodeに追加したsum.framework(macosディレクトリ以下に配置した物)を使いたい場合は、sum.dartlibraryPathを、テスト時のみmacos/sum.framework/sumとなる様な形に処理を加えてください

Android

公式ではndk-buildスクリプトやcmakeコマンドで作る方法(参考)が紹介されていますが、
今回は Android Studio で作ります

Native C++ プロジェクトを作成します

android-ss0.png

app/src/main/cppディレクトリに移動し、native-lib.cpp を削除して代わりに sum.csum.h を配置します

CMakeLists.txt を以下の内容に書き換えます

CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)

project("sum")

add_library(sum SHARED sum.h sum.c)

app/build.gradlecom.android.applicationcom.android.library に変更し、applicationIdの行を削除します

app/build.gradle
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"

// 以下略

この状態で BuildMake Projectをすると、
app/build/outputs/aarディレクトリの中にapp-debug.aarというファイルが作成されます

android-ss1.png

拡張子は.aarですが、実際はただのzipファイルなので解凍ソフトで開けます。
開けない場合は拡張子を.zipにしてください。
また、何故かmacOSの標準アプリだと開けないので自分は The Unarchiver で開きました

ズラッとディレクトリが並んでいます

android-ss2.png

中にあるjniフォルダをjniLibsという名前に変えて、Flutterプロジェクトの
android/app/src/mainに配置します

これで Android でも動作する様になります

ちなみにNative C++ プロジェクトを作成した際に自動で作られるJavaやKotlinのファイルは不要なので、気になる場合は以下の手順で削除も出来ます

  1. app/src/main/javaapp/src/main/resapp/src/testapp/src/androidTest ディレクトリを削除
  2. app/src/main/AndroidManifest.xml から <application>〜</application>の部分を削除
  3. build.gradle(appディレクトリでは無い方)から、テスト関連の行を削除
    testで検索して、ヒットした行を全て削除すればOKです)

Windows

公式ドキュメントにはまだWindows版に関しての記載はない為
あくまで私的に動作を確認したものとなっております。
実使用の際にはご留意の上でお願いいたします。

Visual Studio 2019 をインストールします。無料のコミュニティ版で構いません。
インストール時にオプションで「C++ CMake ツール」を追加する様にします。
https://visualstudio.microsoft.com/ja/downloads/

次に、cmake でビルドをする為の準備をします。
Android と同じ様に、CMakeLists.txtsum.csum.hを同じフォルダに配置します

加えて、sum.defを新しく作成します

sum.def
EXPORTS
	sum

CMakeLists.txtを以下の内容にします。
Androidとほぼ同じ内容ですが、sum.defが追加されています

CMakeLists.txt
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を起動します

windows-ss0.png

先ほどの 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 windowflutter 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 初公開
  1. iOSに対してはFlutterのドキュメントでは、 DynamicLibrary.open ではなく DynamicLibrary.process または DynamicLibrary.execute の使用を推奨しています。理由としてはAppleの審査が通るか不明だからとの事ですが、実際にDynamicLibrary.openを用いたアプリを申請し、問題なく通った為こちらの方法を記述しています。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6