背景
- Flutter で Linux と Android/iOS で似た UI のアプリを作りたい.
- ネイティブ関数(C/C++)呼びたい.
- 特に Vulkan や Embree などのグラフィックスコード
最近(?)の Flutter では Desktop アプリも作れるようになりました,
しかし, まだベータであり, またプロジェクトキャンセルのリスクもあります.
そんな不安に怯えつつも, 使えれば便利そうですし(UI 開発などでは既存の Flutter エコシステムを使える), 他に良さげな代替は無いようなので, 手を出してみます.
Linux を基本とします. ちょっといじれば Windows, macOS でも動くと思われます.
セットアップ
にあるように手順を踏んでおきます. dev
が推奨のようですが, 今回は master
を使いました.
特段問題なければ, example で flutter run
するとアプリが起動するはずです.
native 関数を呼び出す.
native モジュールを作って呼び出してみます.
dart ffi を介してなので, 特段いままで他のプロジェクト(C, Python, etc)での ffi のやりかたとあまり変わりないかと思います.
を参考にします. 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 にすると小さくなると思われますが,
現状 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 で出てくるコマンドを単体で実行するだけでは連携してくれませんでした)
にあるように, スタンドアロン実行で 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 ファイルを実際に読み込むようですね.
ちなみに RTLD_LAZY
で読んでますね.
TODO
-
似たようなやりかたで Android で native code 呼び出しを試す. https://flutter.dev/docs/development/platform-integration/c-interop にあるように Android/iOS でも動くはず.
-
Termiux あたりで .so ビルドするようにして,
/sdcard
あたり経由で native .so もリロードしやすくする(毎回クロスコンパイル + adb 転送がめんどい)
-
Termiux あたりで .so ビルドするようにして,
- .so の推奨配置場所を調べる
-
Vulkan API を ffi 経由で呼ぶ
- Flutter の画面コンテキストを Vulkan に渡せるか調べる(Flutter engine をいじらねばならないか?)
-
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 若人さまへと昇華なされるスキームを確立する旅に出たい.