Flutter/Dart における FFI

Flutter Meetup Tokyo #10

今日話すこと

dart:ffi の実装背景と課題

FFI ?

Foreign function interface


今回は C 呼び出しの話

自己紹介

自己紹介

@sensuikan1973

sensuikan1973 Github

低レイヤの習熟度低いですが、頻繁に FFI と付き合う運命にあるので調べました

自己紹介

w:800

自己紹介

お家で作ってるモノ

center w:800

自己紹介

オセロには常に C が必要

自己紹介

center w:800

自己紹介

つらい...疲れた...重そう...

extern "C" void
    JNICALL
Java_com_done_sensuikan1973_othellode_LibEdax_nativeEdaxGetHintList(
        JNIEnv *env,
        jobject thiz,
        jint hintNum,
        jbooleanArray isBookMoveArray,
        jintArray scoreArray,
        jintArray bitPlaceArray
        )
{
    auto hint_list = HintList();
    edax_hint(hintNum, &hint_list);
    jboolean *isBookMoveArrayPointer = env->GetBooleanArrayElements(isBookMoveArray, 0);
    jint *scoreArrayPointer = env->GetIntArrayElements(scoreArray, 0);
    jint *bitPlaceArrayPointer = env->GetIntArrayElements(bitPlaceArray, 0);
    const int MAX_HINT_NUM = 34;
    const int NOT_SEARCH_RESULT_BIT_PLACE = -1;
    for (int i = 0; i < MAX_HINT_NUM; i++){
        const auto hint = hint_list.hint[i];
        isBookMoveArrayPointer[i] = hint.book_move;
        scoreArrayPointer[i] = hint.score;
        bitPlaceArrayPointer[i] = hint.move;
        // 探索結果でない場合は bitPlace に -1 を入れて呼び出し側に伝える
        if (hint.pv->n_moves == 0) {
            bitPlaceArrayPointer[i] = NOT_SEARCH_RESULT_BIT_PLACE;
        }
    }
    env->ReleaseBooleanArrayElements(isBookMoveArray, isBookMoveArrayPointer, 0);
    env->ReleaseIntArrayElements(scoreArray, scoreArrayPointer, 0);
    env->ReleaseIntArrayElements(bitPlaceArray, bitPlaceArrayPointer, 0);
}
前置き

各言語の C 呼び出し

前置き

代表的なもの

言語 実装方法
Java
JNIJNA, SWIG を使う
Go
cgo を使う
Python
ctypescffi を使う
Rust
extern キーワードで容易に呼べる
Ruby
Ruby-FFI を使う
Javascript
WebAssembly を使う
Swift

Dart は?

Dart から C を呼ぶ方法
(これまで)

Dart から C を呼ぶ方法 (これまで)

Native Extension

Dart から C を呼ぶ方法 (これまで)

Dart 側


library sample_hello;
import 'dart-ext:sample_hello';
void hello() native "Hello";

参考: dart-lang sample_extension

Dart から C を呼ぶ方法 (これまで)

C++ 側 (一部省略)

DART_EXPORT Dart_Handle sample_hello_Init(Dart_Handle parent_library) {
  if (Dart_IsError(parent_library)) return parent_library;
  Dart_Handle result_code = Dart_SetNativeResolver(parent_library, ResolveName, NULL);
  if (Dart_IsError(result_code)) return result_code;
  return Dart_Null();
}

void hello(Dart_NativeArguments arguments) {
  Dart_EnterScope();
  printf("Hello\n");
  Dart_ExitScope();
}

Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool* auto_setup_scope) {
  if (!Dart_IsString(name) || auto_setup_scope == NULL) return NULL;
  Dart_EnterScope();
  const char *cname;
  Dart_StringToCString(name, &cname);
  Dart_NativeFunction result = NULL;
  if (strcmp(cname, "hello") == 0) result = hello;
  Dart_ExitScope();
  return result;
}

👉 深いレベルで拡張可能
👉 都度 ResolveName する

Dart から C を呼ぶ方法 (これまで)

わかりやすく例をもう一個

void isEven(Dart_NativeArguments arguments) {
  Dart_EnterScope();
  Dart_Handle arg1 = Dart_GetNativeArgument(arguments, 0);
  int64_t input;
  if (Dart_IsError(Dart_IntegerToInt64(arg1, &input)))
  {
    Dart_ThrowException(Dart_NewStringFromCString("Error だよ"));
  }
  Dart_SetReturnValue(arguments, Dart_NewBoolean(input % 2 == 0));
  Dart_ExitScope();
}

👉 引数と返り値の型情報が静的に定義されていない

さて、Flutter では?

Flutter から C を呼ぶ方法

現状、Swift/Objective-C, Kotlin/Java を経由する必要がある

Flutter から C を呼ぶ方法

center

Flutter から C を呼ぶ方法

たくさんの 👍 の思いは?

Flutter から C を呼ぶ方法

① 既存ソフトをより統合しやすくしてほしい

Flutter から C を呼ぶ方法


◯ 大量のグルーコードがつらい



◯ 低オーバーヘッドがいい

Flutter から C を呼ぶ方法

SQLite

Realm

OpenCV

crypto, ssh ... libraries

などが具体例として挙げられている

Flutter から C を呼ぶ方法

② 大量のデータを効率よく出し入れしたい



なお、Dart 2.4 から TransferableTypedData が使用できるようになったので、ある程度はそれで間に合いそう

こういう要望にどう応えるか?

Flutter/Dart における Dart->C をどう実現するか?

「Native Exstention でいいんじゃないの...?」

w:1000

Flutter/Dart における Dart->C をどう実現するか?

😣


Dart VM FFI Vision に理由が述べられていた
Flutter/Dart における Dart->C をどう実現するか?

【 理由 1 】

名前ベースの API

// dart-lang/sdk/runtime/include/dart_api.h より引用
DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
Dart_SetField(Dart_Handle container, Dart_Handle name, Dart_Handle value);

👉 名前解決がキャッシュされない

👉 AOT コンパイラに厳しい
(最悪の場合を想定したり、手動でアノテーションを付けてまわったりしないといけない)

Flutter における Dart->C をどう実現するか?

【 理由 2 】

Reflective Marshaling は効率良くない

void isEmailAddress(Dart_NativeArguments arguments)

void arguments 👀

⇒ 引数/返り値が静的に型付けされた上での Marshaling の方が効率良い

⇒ その点は FFI が優れている

そこで、dart : ffi 👍



https://github.com/dart-lang/sdk/tree/master/sdk/lib/ffi
dart:ffi

Google I/O'19 でも言及あり

center

We are working on a new foreign function interface.
This should help you reuse existing C and C++ code,
which is important for some critical stuff

dart:ffi

ちなみに

we expect that moving Flutter Engine from C API to FFI should significantly reduce overheads associated with crossing the boundary between Dart and native code

dart:ffi

どう使えるのか?

dart:ffi

👍

import "dart:ffi" as ffi;
import 'dart:io' show Platform;

void main() {
  final libHelloWorld = ffi.DynamicLibrary.open("./libHelloWorld.dylib");
  final helloWorld = libHelloWorld.lookupFunction
  	<ffi.Void Function(), void Function()>("helloWorld");

  helloWorld();
}

https://github.com/sensuikan1973/Dart_FFI_Hello_World

dart:ffi

ちなみに、先週、

Flutter stable 版に入った


(Android のみで試験的に触れる)

dart:ffi

どういう構成になるのか

dart:ffi

w:850

👉 Bindings: final helloWorld = libHelloWorld.lookupFunction<ffi.Void Function(), void Function()>("helloWorld"); みたいなのを定義するレイヤーのこと

課題をいくつか紹介

dart:ffi

1: 例外を拾えない

⇒ C レイヤーを追加実装する

w:850

dart:ffi

2: CFE への追加実装

w:850

補完や静的解析を行うために、
CFE (Common Front-End) への追加実装が必要。

*Dart2 VM からは、生のソースから Dart を直接実行できず、CFE によって生成された Kernel Binary(dill) を与える必要がある

dart:ffi

3: サポート対象のプラットフォーム

待ちきれない人がスケジュールを聞く
⇒ 具体的なスケジュールは示せない。待って。
w:850

dart:ffi

4: HotReload 下での callback の挙動は..?

w:600
w:650

dart:ffi

その他タスクの詳細は
Dart VM FFI projects を参照


正直に言いますと、低レイヤの勉強不足で、
あまり理解できてないものが多い...

詳しい方は是非 dart:ffi に FB を送りましょう 👍

ありがとうございました

リンク一覧