Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Flutterプラグインでdart:ffiを使ってみる

 Flutterプラグインでdart:ffiを使ってみる

dart:ffiを使ってみる導入と、細かな部分の紹介。

Takashi Kawasaki

December 10, 2019
Tweet

More Decks by Takashi Kawasaki

Other Decks in Programming

Transcript

  1. ⾃⼰紹介 • 川崎 ⾼志 (@espresso3389) • クミナス株式会社 代表取締役 CEO •

    恵⽐寿の会社です • なんでもやる⼈ • Flutterは好きなんだけどどちらかというと底レイヤー担当
  2. 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
  3. Flutterプラグインでdart:ffiを使ってみる • テンプレを作成 • flutter create ‒t plugin hello_ffi •

    実質的なコード • ios/Classes/hello.cpp (追加) • lib/hello_ffi.dart (書き換え) • 調整するファイル • android/CMakeLists.txt (追加) • android/build.gradle (書き換え)
  4. ios/Classes/hello.cpp • iOSの下にあるけど、Androidでも同じファイルをビルドして利 ⽤する(Xcodeの制限に合わせた運⽤) #include <stdint.h> // 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; }
  5. 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<NativeFunction<Int32 Function(Int32, Int32)>>("native_add") .asFunction();
  6. android/CMakeLists.txt • Androidでのモジュールのビルド⼿順を定義(CMake) cmake_minimum_required(VERSION 3.4.1) add_library( hello # 共有ライブラリにする SHARED

    # Xcode側でファイルの位置に対する制約があるので、 # Android側は無理やり ios 配下のファイルを引き込む ../ios/Classes/hello.cpp )
  7. 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" }
  8. 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)}'), ), ), ); } }
  9. iOSでのポイント • ios/Classes/hello.cpp, lib/hello_ffi.dart の追加/書き換え • cd example; flutter run

    するだけという簡単さ • C/C++のプログラムはアプリ本体に静的リンクされる • DynamicLibrary.process() でロードする
  10. 動的ロード // 現在実⾏中のモジュール DynamicLibrary exe = DynamicLibrary.executable(); // モジュールのロード DynamicLibrary

    lib = DynamicLibrary.open("libXXX.so"); // 現在のプロセスのすべてのグローバルシンボルを持つ奴? // (Windowsじゃ使えないらしい) DynamicLibrary proc = DynamicLibrary.process(); // ハンドル値 Pointer<Void> handle = lib.handle;
  11. 関数のインポート(lookup) // C subtract function - int subtract(int *a, int

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

    b); // lookup final funcPtr = lib.lookup<NativeFunction<SubtractAbi>>('subtract'); final subtract = funcPtr.asFunction<Subtract>(); // lookupFunction (ちょっと簡単?) final subtract2 = lib.lookupFunction< NativeFunction<SubtractAbi>, Subtract>('subtract');
  13. ポインタ演算 // アドレスからポインタを作成(危険な例w) final p8 = Pointer<Uint8>.fromAddress(0xdeadbeef); // アドレス表⽰ print(ptr.address);

    // Pointer<Uint32> にキャスト Pointer<Uint32> p32 = p8.cast<Uint32>(); // メモリに直接アクセスできる Uint32List (⻑さは⾃分で与える) Uint32List list = p32.asTypedList(length); // 当然、普通のインデックスアクセスはできる Uint32 v = p32[0]; p32[0] = 32;
  14. ⽂字列 // Utf8 from ffi package // C側から貰ったUTF-8⽂字列(char*) Pointer<Utf8> p

    = .... String s = fromUtf8(p); int length = strlen(p); // Dart側から Pointer<Utf8> q = toUtf8("ほげほげ");
  15. 構造体 // 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<Coordinate>().ref ..latitude = latitude ..longitude = longitude; }
  16. 構造体 // C/C++ struct Place { char *name; struct Coordinate

    *coordinate; }; // Dart class Place extends Struct { Pointer<Utf8> name; Pointer<Coordinate> coordinate; factory Place.allocate(Pointer<Utf8> name, Pointer<Coordinate> coordinate) => allocate<Place>().ref ..name = name ..coordinate; }
  17. 構造体 • まだ基本的なことしかできない • 構造体のネスト • #37271 [ffi] Nested structs

    • 構造体のパッキング • #38158 FFI support for packed structs • プラットフォーム依存の構造体のレイアウト • #35768 dart:ffi platform-dependent struct layout