Slide 1

Slide 1 text

Flutterプラグインでdart:ffi を使ってみる 川崎 ⾼志 @espresso3389 クミナス株式会社

Slide 2

Slide 2 text

⾃⼰紹介 • 川崎 ⾼志 (@espresso3389) • クミナス株式会社 代表取締役 CEO • 恵⽐寿の会社です • なんでもやる⼈ • Flutterは好きなんだけどどちらかというと底レイヤー担当

Slide 3

Slide 3 text

dart:ffiって何?

Slide 4

Slide 4 text

dart:ffiって何? • FFI: Foreign Function Interface • 他の⾔語で書かれた関数(API)を呼び出す仕組み • Dart:ffiはDartから主にC⾔語のシグニチャを持つ関数を呼びだす • dart:ffiが提供する機能 • C⾔語で実装された関数を呼び出す機能 • メモリ上の特定アドレス(ポインタ)に直接書き込む機能 • メモリ上の特定アドレス(ポインタ)から直接読み込む機能 • Dart上で作ったListを直接渡すことはできない(GC周りの⾯倒を回避?) • Dart 2.7≒Flutter 1.12.x (beta)なら既に使える • https://api.dartlang.org/stable/2.7.0/dart-ffi/dart-ffi-library.html

Slide 5

Slide 5 text

Flutterプラグインでdart:ffiを使ってみる • Binding to native code using dart:ffi • https://flutter.dev/docs/development/platform-integration/c- interop

Slide 6

Slide 6 text

Flutterプラグインでdart:ffiを使ってみる • テンプレを作成 • flutter create ‒t plugin hello_ffi • 実質的なコード • ios/Classes/hello.cpp (追加) • lib/hello_ffi.dart (書き換え) • 調整するファイル • android/CMakeLists.txt (追加) • android/build.gradle (書き換え)

Slide 7

Slide 7 text

ios/Classes/hello.cpp • iOSの下にあるけど、Androidでも同じファイルをビルドして利 ⽤する(Xcodeの制限に合わせた運⽤) #include // extern "C" はお約束 // __attribute__((visibility("default"))) はこの関数が外部から参照可能 にする // __attribute__((used)) はこの関数がリンク時に削除されないようにする extern "C" __attribute__((visibility("default"))) __attribute__((use d)) int32_t native_add(int32_t x, int32_t y) { return x + y; }

Slide 8

Slide 8 text

lib/hello_ffi.dart • hello.cppの関数実装をDartの世界に引き込む import 'dart:ffi'; import 'dart:io' as io; final DynamicLibrary _module = io.Platform.isAndroid ? DynamicLibrary.open("libhello.so") // Androidでは共有ライブラリ (libXXXX.so) : DynamicLibrary.process(); // iOSではプロセスのモジュールを開く // native_add を関数として引き込む final int Function(int x, int y) nativeAdd = _module .lookup>("native_add") .asFunction();

Slide 9

Slide 9 text

android/CMakeLists.txt • Androidでのモジュールのビルド⼿順を定義(CMake) cmake_minimum_required(VERSION 3.4.1) add_library( hello # 共有ライブラリにする SHARED # Xcode側でファイルの位置に対する制約があるので、 # Android側は無理やり ios 配下のファイルを引き込む ../ios/Classes/hello.cpp )

Slide 10

Slide 10 text

Android/build.gradle • CMakeLists.txt をビルド対象として設定する android { compileSdkVersion 28 sourceSets { main.java.srcDirs += 'src/main/kotlin' } externalNativeBuild { // ここから同⼀ディレクトリ内にあるCMakeLists.txtを使ってcmakeを実⾏する cmake { path "CMakeLists.txt" } } defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }

Slide 11

Slide 11 text

example/lib/main.dart (使う⼈) class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('dart:ffi plugin example app' ), ), body: Center( child: Text('4+5=${nativeAdd(4, 5)}'), ), ), ); } }

Slide 12

Slide 12 text

iOSでのポイント • ios/Classes/hello.cpp, lib/hello_ffi.dart の追加/書き換え • cd example; flutter run するだけという簡単さ • C/C++のプログラムはアプリ本体に静的リンクされる • DynamicLibrary.process() でロードする

Slide 13

Slide 13 text

Androidでのポイント • ソースコードは ios/Classes に置く(ちょっとダサい) • CMakeでビルド • 実際には、gradleさんにCMakeでビルドよろしく!ってお願い するだけ

Slide 14

Slide 14 text

dart:ffiのよさげなところ • C/C++でシングルソースでiOS/Androidでビルドすることがで きる(※がんばれ) • Objective-C/Swift/Java/Kotlinのコードを⼀切触らない • Platform Channelとか難しいのがない(ごっそり削除した) クソ簡単!

Slide 15

Slide 15 text

dart:ffiのサンプル(Dart) • dart-lang/samples/ffi • https://github.com/dart-lang/samples/tree/master/ffi • hello_world • 基本型 • 構造体の使い⽅ • system関数を呼ぶサンプル

Slide 16

Slide 16 text

dart:ffiの内容 • dart:ffi library • https://api.dart.dev/stable/2.7.0/dart-ffi/dart-ffi-library.html • 基本型とポインタ、構造体の定義 メモリ確保とか⽂字列の処理は・・・・?

Slide 17

Slide 17 text

ffiパッケージ

Slide 18

Slide 18 text

ffiパッケージ • ffi 0.1.3 • https://pub.dev/packages/ffi • dart:ffiに不⾜している機能を提供 • allocate • free • Utf8 • Utf16

Slide 19

Slide 19 text

動的ロード // 現在実⾏中のモジュール DynamicLibrary exe = DynamicLibrary.executable(); // モジュールのロード DynamicLibrary lib = DynamicLibrary.open("libXXX.so"); // 現在のプロセスのすべてのグローバルシンボルを持つ奴? // (Windowsじゃ使えないらしい) DynamicLibrary proc = DynamicLibrary.process(); // ハンドル値 Pointer handle = lib.handle;

Slide 20

Slide 20 text

関数のインポート(lookup) // C subtract function - int subtract(int *a, int b); // C側での関数の定義に対応する定義 typedef SubtractAbi = Int32 Function(Pointer a, Int32 b); // Dart側で使いたい関数定義 (int のサイズに注意!) typedef Subtract = int Function(Pointer a, int b); // allocate from ffi package Pointer p = allocate(1); p.value = 3; final funcPtr = lib.lookup>('subtract'); final subtract = funcPtr.asFunction(); int result = subtract(p, 5);

Slide 21

Slide 21 text

関数のインポート(lookupFunction) // C subtract function - int subtract(int *a, int b); // lookup final funcPtr = lib.lookup>('subtract'); final subtract = funcPtr.asFunction(); // lookupFunction (ちょっと簡単?) final subtract2 = lib.lookupFunction< NativeFunction, Subtract>('subtract');

Slide 22

Slide 22 text

ポインタ演算 // アドレスからポインタを作成(危険な例w) final p8 = Pointer.fromAddress(0xdeadbeef); // アドレス表⽰ print(ptr.address); // Pointer にキャスト Pointer p32 = p8.cast(); // メモリに直接アクセスできる Uint32List (⻑さは⾃分で与える) Uint32List list = p32.asTypedList(length); // 当然、普通のインデックスアクセスはできる Uint32 v = p32[0]; p32[0] = 32;

Slide 23

Slide 23 text

⽂字列 // Utf8 from ffi package // C側から貰ったUTF-8⽂字列(char*) Pointer p = .... String s = fromUtf8(p); int length = strlen(p); // Dart側から Pointer q = toUtf8("ほげほげ");

Slide 24

Slide 24 text

構造体 // C/C++ struct Coordinate { double latitude; double longitude; }; // Dart class Coordinate extends Struct { @Double() double latitude; @Double() double longitude; factory Coordinate.allocate(double latitude, double longitude) => allocate().ref ..latitude = latitude ..longitude = longitude; }

Slide 25

Slide 25 text

構造体 // C/C++ struct Place { char *name; struct Coordinate *coordinate; }; // Dart class Place extends Struct { Pointer name; Pointer coordinate; factory Place.allocate(Pointer name, Pointer coordinate) => allocate().ref ..name = name ..coordinate; }

Slide 26

Slide 26 text

構造体 • まだ基本的なことしかできない • 構造体のネスト • #37271 [ffi] Nested structs • 構造体のパッキング • #38158 FFI support for packed structs • プラットフォーム依存の構造体のレイアウト • #35768 dart:ffi platform-dependent struct layout

Slide 27

Slide 27 text

構造体のレイアウト

Slide 28

Slide 28 text

現状のまとめ • ポインタ渡せるので基本、何でもできる • メモリ確保系は ffi パッケージを使う • ⽂字列変関係も ffi パッケージを使う • 構造体のサポートは最低限 みんな、がんばれ!