Flutter Desktop + native code アプリを作る準備メモ(2020 年 1 月 7 日時点)

## 背景

* Flutter で Linux と Android/iOS で似た UI のアプリを作りたい.
* ネイティブ関数(C/C++)呼びたい.
  * 特に Vulkan や Embree などのグラフィックスコード

https://github.com/google/flutter-desktop-embedding

最近(?)の Flutter では Desktop アプリも作れるようになりました,

しかし, まだベータであり, またプロジェクトキャンセルのリスクもあります.
そんな不安に怯えつつも, 使えれば便利そうですし(UI 開発などでは既存の Flutter エコシステムを使える), 他に良さげな代替は無いようなので, 手を出してみます.

Linux を基本とします. ちょっといじれば Windows, macOS でも動くと思われます.

## セットアップ

https://github.com/google/flutter-desktop-embedding

にあるように手順を踏んでおきます. `dev` が推奨のようですが, 今回は `master` を使いました.

特段問題なければ, example で `flutter run` するとアプリが起動するはずです.

## native 関数を呼び出す.

native モジュールを作って呼び出してみます.
dart ffi を介してなので, 特段いままで他のプロジェクト(C, Python, etc)での ffi のやりかたとあまり変わりないかと思います. 

https://flutter.dev/docs/development/platform-integration/c-interop

を参考にします. dart 側のモジュールのみで完結するようで, flutter 独自の import は不要です.

flutter の boiler plate 生成コマンド

```
$ flutter create --template=plugin native_add
```

は, 現状 android/ios のみで, linux/macos/Windows 向けにはなにも生成しませんので不要です.

実際のところ, native 側のコンパイルは通常の .so を作るので OK なので flutter に合わせてなにかコンパイルを変えるとかは無いです.

以下のような感じでコンパイルしておきます.

```
$ gcc -fPIC -shared native_add.cc -o libnative_add.so
```

あとは上記 URL のように, Dart コード側で .so のロードとバインディングを書きます.

```
import 'dart:ffi';  // For FFI
import 'dart:io';   // For Platform.isX

final DynamicLibrary nativeAddLib = DynamicLibrary.open("libnative_add.so")

...
```

.so は, Linux の場合なにも prefix が無ければ `flutter run` したカレントディレクトリから検索します(`dlopen` と同じ動作?)
セキュリティを高めた Linux を使っている場合は `LD_LIBRARY_PATH` でパスを通しておく必要があるかもしれません.

.so を変更した場合, flutter の hot-reload は効かず, 再度 `flutter run` する必要があるようです.

flutter(dart)経由で ffi 呼び出す場合は, そこそこ型チェックなどしてくれるようなのでデバッグはやりやすそうです.

## Flutter desktop の中身はどうなっているのか?

Linux 環境でお話します.

現在(2019/12 1.13)では, glfw を使った engine が flutter 側に用意されています.
(glfw なのは, Vulkan を使う用?)

`flutter-desktop-embedding` の example で `flutter run` すると, `linux` フォルダにあるところのコードがコンパイルされます(main 関数の実装がある).

出来た exe は

`build/linux/debug/flutter_desktop_example` 

に配置されています. あとはこのコマンドを実行しているだけのようです. 
試しに, `flutter_desktop_example` を直接叩いても動きます.

`flutter_desktop_example` を ldd すると,

```
	linux-vdso.so.1 (0x00007fff365d9000)
	libflutter_linux_glfw.so => /home/syoyo/work/flutter-desktop-embedding/example/build/linux/debug/./lib/libflutter_linux_glfw.so (0x00007f604d2b1000)
	libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f604ced1000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f604cb33000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f604c91b000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f604c52a000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f604c326000)
	libGL.so.1 => /usr/lib/x86_64-linux-gnu/libGL.so.1 (0x00007f604c09a000)
	libX11.so.6 => /usr/lib/x86_64-linux-gnu/libX11.so.6 (0x00007f604bd62000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f604bb43000)
	librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f604b93b000)
	libXcursor.so.1 => /usr/lib/x86_64-linux-gnu/libXcursor.so.1 (0x00007f604b731000)
	libXinerama.so.1 => /usr/lib/x86_64-linux-gnu/libXinerama.so.1 (0x00007f604b52e000)
	libXrandr.so.2 => /usr/lib/x86_64-linux-gnu/libXrandr.so.2 (0x00007f604b323000)
	libXxf86vm.so.1 => /usr/lib/x86_64-linux-gnu/libXxf86vm.so.1 (0x00007f604b11d000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f604eff6000)
	libGLX.so.0 => /usr/lib/x86_64-linux-gnu/libGLX.so.0 (0x00007f604aeec000)
	libGLdispatch.so.0 => /usr/lib/x86_64-linux-gnu/libGLdispatch.so.0 (0x00007f604ac36000)
	libxcb.so.1 => /usr/lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f604aa0e000)
	libXrender.so.1 => /usr/lib/x86_64-linux-gnu/libXrender.so.1 (0x00007f604a804000)
	libXfixes.so.3 => /usr/lib/x86_64-linux-gnu/libXfixes.so.3 (0x00007f604a5fe000)
	libXext.so.6 => /usr/lib/x86_64-linux-gnu/libXext.so.6 (0x00007f604a3ec000)
	libXau.so.6 => /usr/lib/x86_64-linux-gnu/libXau.so.6 (0x00007f604a1e8000)
	libXdmcp.so.6 => /usr/lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f6049fe2000)
	libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f6049dcd000)
```

となり, flutter 関連は `libflutter_linux_glfw.so` だけに依存しています.

このバイナリと `libflutter_linux_glfw.so` と, あとはリソースファイルをまとめれば,
flutter SDK に依存せず再配布できるアプリパッケージが作れると思われますが, app が X11 関連の .so に依存しているのでちょっと難しいでしょうか. とくに GLX に依存しているので, SwiftShader で差し替えて CPU 描画というのも無理そうです.

glfw の部分を書き直すか(https://github.com/ds84182/flutter_sdl で SDL ベースのがありますが, いくらか古くて最新 flutter では動作しません), AppImage 形式で配布するのも考えたほうがいいかもしれません.

ちなみに libflutter_linux_glfw.so のファイルサイズは 93 MB でした.

```
-rwxr-xr-x 1 syoyo syoyo  93M Dec 21 18:07 libflutter_linux_glfw.so
```

release にすると小さくなると思われますが, 

https://github.com/google/flutter-desktop-embedding#example

現状 windows, linux では debug ビルドのみになっています. もう少し成熟を待つ必要がありますね(プロジェクトが打ち切りにならないことが前提)

## OpenGL/Vulkan などを描画したい

Texture 経由がよさそうです.

Rendering “External Texture”: A Flutter Optimization Story
https://hackernoon.com/rendering-external-texture-an-flutter-optimization-by-alibaba-c5ed143af747

Support Texture widget #107
https://github.com/google/flutter-desktop-embedding/issues/107

Texture support for glfw. #9822
https://github.com/flutter/engine/pull/9822

cloudwebrtc が頑張ってくれています.

現状で Texture を Flutter Desktop で使いたい場合は cloudwebrtc のフォークを使うのがよさそうです.


## VSCode などと連携して App を hot reload したい

native コードを呼び出せるのは確認しましたが, ネイティブコードの結果を可視化などでインタラクティブに Flutter 側の UI 開発とかしたいですよね.

`flutter run` ではコンソールでキーボード入力でリロード, `flutter_desktop_example` で起動だとコンパイル済み dart コードを実行するので, `flutter_desktop_example` 単体では hot reload 機能はありません. 

http 経由でなにやらする必要があります(`flutter run` ではこのあたり一式処理していてどのようなコマンドを実行しているのか, ソースコードをみないと不明. 単に ps で出てくるコマンドを単体で実行するだけでは連携してくれませんでした)

https://github.com/canewsin/flutter_desktop_launcher

にあるように, スタンドアロン実行で observatoryUri を取得してうまく繋げれば VSCode 連携できるものと思われます.

`flutter run` は, 中身としてはなにやら dart のプログラムを読んでいるだけなので, dart 単体だけでなにかできるかもしれませんね(flutter コマンド非依存で実現したい, 自前ツールで flutter アプリを制御したい, などのため)

## Native module を hot reload したい

flutter を reload すれば, .so もリロードされることがわかりました(Linux で確認).

dart ffi DynamicLibrary には, `unload` や `close` メソッドはありませんが, Linux の `dlopen`(dart `DynamicLibrary.open` が中でよんでいる関数)では毎回 .so ファイルを実際に読み込むようですね.

https://github.com/dart-lang/sdk/blob/96a8282cd9039d18eeeb066e3c629e19efa5028f/runtime/lib/ffi_dynamic_library.cc#L25

ちなみに `RTLD_LAZY` で読んでますね.

## TODO

* [ ] 似たようなやりかたで Android で native code 呼び出しを試す. https://flutter.dev/docs/development/platform-integration/c-interop にあるように Android/iOS でも動くはず.
  * [ ] Termiux あたりで .so ビルドするようにして, `/sdcard` あたり経由で native .so もリロードしやすくする(毎回クロスコンパイル + adb 転送がめんどい)  
* [ ] .so の推奨配置場所を調べる
* [ ] Vulkan API を ffi 経由で呼ぶ
  * [ ] Flutter の画面コンテキストを Vulkan に渡せるか調べる(Flutter engine をいじらねばならないか?) 
* [x] https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders Flutter Engine(embedder API)と組み合わせ, Electron みたいに Desktop 環境で Standalone のアプリが作れるようにする 
  * => Flutter desktop 自体が embedder API を使っていました.
* [ ] 優秀な Flutter 若人さまが, **人類史上最速で優秀な Flutter Desktop + native C/C++ Vulkan rendering 若人さまへと昇華なされるスキーム**を確立する旅に出たい.