DroidKaigi 2018 Embeddinator-4000セッションの事前資料

2/6追記: スライドも事前公開した。

speakerdeck.com

DroidKaigi 2018でEmbeddinator-4000のセッションをやることになったので、セッションスライドを書きながら、どんな話をして、どんな話をしないか、どのへんがまだ検討中なのか、といったことを、一般参加者のみなさんがセッションを聴講するかしないか判断できるように、事前公開しておこうとおもう。あくまで興味ある人が読んでおいてもいいかなーっていう内容であって、当日はもっと簡単な内容で済ませるかもしれない。なおセッション資料はまだ完成していないのでまだ公開できないのだけど、少なくとも直前には公開しておく予定だ。気が向いたら作りかけを週明けくらいに公開するかも。しないかも。

(コレは何なのかというと、セッションタイトルに「Embeddinator-4000でAndroid Studioから.NETのコードを利用する」とあるように、これはXamarinとは違ってAndroid Studioで作れるJava/Kotlinアプリケーションで.NETのライブラリを使いまわせるものだ。DLL群からaarを作ってついでにmonoランタイムもパッケージして、実行時はmonoでDLLのコードを実行するやつだ。)

セッションの内容の大まかな内容については、現時点でもざっくり書いておこう。

  • e4kとは? Xamarinとは違う?
  • Background: Mono, NDK, JNI
  • demo2つ(どちらもHello Worldレベルのシンプルなやつ)
  • e4kでできること: 完成度と課題
  • e4k以外のバインディング機構の評価軸。特に
    • ランタイムの堅牢性
    • 明確なバインディング仕様
    • IDEのサポート
    • ライブラリ エコシステムの構築

最後の項目はまだ煮詰まっていない。他のバインディング生態系として、Kotlin、Swift、RubyPythonJavaScript/TypeScriptあたりはちょっとでも言及しておきたいとは思っている(ただ、多分深く触れる時間は無い)。

逆に以下は今回は話さない方向性でまとめている:

  • e4kの使い方の説明
  • e4kのツールチェインを実現する仕組み(Clang, CppSharp, JNA)
  • Xamarin.Formsの埋め込み方のstep by step解説
  • .NETに由来するe4kの事情(正直自信ない。何が「出来ない」かをきちんと筋道立てて説明するためには、ここは省けない気もしている)
  • React Native(RNはそもそも今回議題にするバインディング機構ではなく、2日目にRNのアーキテクチャのセッションがあるのでそちらを見るべき。あと自分が詳しくない)

e4kの最新情報(?)

2017年夏期からの差分(ない)

Embeddinator-4000…と書くと長いのでスライドではe4kと書いている…については、実は昨年8月に行われたJXUG名古屋でも90分くらい(!?)しゃべっている。普段は同じ話題を扱うセッションを2回も行うことはしないのだけど、今回は地域から参加者がかぶらなそうなのと、オーディエンスがガチのXamarinユーザーと生粋のJava/Kotlin系のAndroid開発者で、根本的にターゲットを変えて内容も変えるべきなのと、e4kはまだかなりmoving targetだから半年もあればいろいろ変わって実装もいい感じに整理されてくるだろう…という楽観的な考えがあったためだ。

名古屋ではもともと50分程度だったものが、時間が余っているというので引き伸ばして話してしまったものだったので、今回はシュッとこなさなければならない。そういうわけで、前提知識としてe4kの基盤にあるもの(monoとか)について話す部分は、10分程度に圧縮することにした。大圧縮だ。ここをしっかり説明したところで、その後の内容と関わることは少なく、生粋のAndroiderに響くものもたぶん小さいと思って削った。

さらに、いつもなら事前に長大な資料をまとめてからスライドを書き始めるのだけど、e4kについては、既にExtensive Xamarinに30ページ近い文章をかいてしまっていて(この内容はJXUGの内容にそれなりに近い)、今さらまとめ直すことといったら最新情報と、もう少し踏み込んだ調査くらいだ。

「半年も経てばいろいろ変わる」という読みは完全にアテが外れた。新バージョンのリリースが無く、リリース版を前提として考えると、状況は全くと言っていいほど変わっていない。主に変化していたのはObjective-Cサポートまわりで、Android/Javaまわりは実のところ荒野といっていい状況だ。e4kは米国のMSのイベントのキーノートでも取り上げられる程度に本気度高めでコミットしているプロジェクトだけど、プレゼンテーションのためにXamarin.Formsのライブラリを組み込んだデモまで行ったところから、まるで満足したかのように動きがない…というと言い過ぎだが、コードはゆっくり進捗している。

Formsを動かそう→やめとこ

ともあれ、それならそれで現状に基づいてしゃべるしかない。

e4kでXamarin.Formsが動くなら(まあe4kを使いたいような状況でもXamarin.Formsを使うというのは、わたしは筋悪だと思うけど)、他もいろいろ何とかなって使い物になるだろう、と期待してみると痛い目にあう(あった)。e4kは.NETのライブラリをAndroidiOSのネイティブプロジェクトで「使える」ようにするためのものだ。「使う」ためにはライブラリで提供するAPIは使える状態になってくれないと困る。しかしXamarin.Formsのライブラリ…のうちのAndroidプラットフォーム固有のやつ…をバインドしようとすると失敗する。まだe4kはそこまで完成していないのだ(!)

おかしいじゃん、デモでは動いていたのにバインディングの生成に失敗するなんてありえなくない?…と当然思うわけだが、ここで悟らされた。「ライブラリのコードが動く」のと「ライブラリがバインド出来る」のは完全に別の問題なのだ。Formsを使ったアプリケーションとなるライブラリに「バインド」するのは、Formsの膨大なAPI全てを問題なく「バインド」するより、はるかに簡単なのだ。イベントのデモで動いていたe4kの実体はこれなのだ。

今Formsのようなbait & switchのライブラリをバインドして動かすためには、

という作業が必要になる。これをやる価値があるのは、e4kのデモを見せる時くらいだろう。日々の開発でやるようなことではない。

FormsはUIなので、動くものを見せると見栄えがする。だからFormsを見せたい、というのはわかる。ただ、わたしは、自分で使いたい用途で使えないようなものを無理やり動かしてデモで見せても、あまり意味がないと思っている。

ライブラリの異言語バインディングを問題なく生成するのは非常に難しい。Xamarinでも特に難しい作業のひとつだ。e4kでも難しいのだが、e4kの状況はさらに厳しく、Xamarin.Androidなら可能なAPIメタデータの操作が、e4kではサポートされていない。

再利用可能なエコシステムを構築する(あるいはそれなしでやっていく)ということ

ともあれ、バインド出来ないものがあるのは仕方ない。利用可能なAPIの口だけを用意したライブラリを作って、アプリケーション上ではそれを再利用して組み込めば良い…と思うと、そこにも罠がある。ひとつのAPKに含めることができるe4k由来のaarはひとつだけなのだ。アプリケーションに必要なDLLは全てひとつのe4k呼び出しで生成されるaarにまとめなければならない。この時点で、単独のaarとしての再利用性はほぼ無くなった。単独でMavenに上げて役に立つようなものは、まだ作れない。あくまで自分のアプリケーションの中で使うしかない。

逆に、そこまで割り切っても、ついFormsで実装してしまった複雑なUIを使いまわすことは可能そうなので、技術的にはやはり相応の価値はある。わたしのとりあえずの目的はmscorlib.dllをバインドできて、ろくにリンクしない状態でもaarに含められるようになることだが(と言っても実装は他のエンジニアが仕切っているので、わたしはせいぜいissueを報告したりたまにパッチを書く程度にとどめている)、そこまで出来るようになって、あとライブラリの依存関係と再利用性の問題が解消できれば、それなりにエコシステムを築く土台にはなると考えている。

あと、今回はそんなわけで応用ライブラリのバインディングには手を出さなかったのだけど、e4kで使えるようになってほしいライブラリ群として一連のXamarin Pluginsがある。Xamarinを知らない人向けにひとことで書くと、クロスプラットフォームAPIで、実装側ではプラットフォーム固有のAPIを使えるようにしてくれるという仕組みなので、同じコードがiOS向けのe4kでサポートされるようになれば、きっと楽になるはずだ。ただこれを実現するためには、きちんとbait & switchの仕組みをEmbeddinator-4000.exeが解決できなければならない。今はそこまで出来ていない(はず)。

…という話を本当は踏み込んでやりたいのだけど、多分これは生粋のAndroiderを置いてきぼりにするだけなので、この辺は軽く触れる程度にとどめておこうと思う。

e4kの外側

ここから先はe4k以外のエコシステムに敷衍して話せるような、一般化した話をしたいと思っているのだけど、正直現時点でもどういう議論の方向性にするかは決めていない。あと話の性質的に「オチ」がつかないので(e4k自体については↑の通りで十分にオチがついていると思っている)、最後がgdgdになる可能性がけっこうある。まあ、でも各論で終わるなら避けられないだろう…

隣の芝生は青く見える? 青臭く見える?

ここまでe4kについてだいぶ客観的に評価してきたつもりだけど、e4kはまだemerging technologyであり、ここから何をやっていく必要があるかは見通しておきたい。幸いわれわれはXamairnをゼロから構築してきたので、どういうものが必要になってくるかはだいたい分かっている。

そしてこれは似たような言語間のバインディング機構を構築しようとしている人々にとっても、きっと役に立つ話だ。

プラットフォームAPIへのアクセス: やりたい? やりたくない?

swiftを素材に話を始めよう。今、SwiftをAndroidに持ってきて動かそうとしている人は何人かいる(Swift for Android、SwiftJava、Silverなど、プロジェクトが複数ある)。SwiftをAndroidで動かす目的が、Swiftでアプリケーションを全部書くことだとしたら、今あるものでは全然足りない。AndroidのアプリケーションはJavaAPIを駆使しなければ書けない。SwiftはAppleLLVMエコシステムで発展してきた言語だから、同じLLVMに根差すことになったAndroid NDKとは親和性が高いけど、Xamarinが「全てをC#で」書くのと同じ意味で「全てをSwiftで」書こうと思ったら、Java APIとの相互運用は避けられない。逆にAndroid APIにアクセスしなくてもいい、Swiftの標準ライブラリだけ使えれば良い、というのであれば、現状で十分だろう。

プラットフォームAPIにアクセスしなくてよいのであれば、Pythonなどは確か2010年頃にはもう動いていたし(黒いターミナルもどきが出るやつだ)、V8は動くのだからJSエンジンを使った機構を作るのは(相対的には)簡単だ。

プラットフォームAPIをサポートすると決めたら、バインディングを用意してやる必要がある。手作業でバインドするやり方と、ツールで自動化するやり方だ。後者はとても難しいのだけど、どこかの時点で必ず必要になる。いずれサードパーティのライブラリをサポートする必要が生じるからだ。頻繁に更新されるAndroidサポートライブラリを手作業でメンテする労力を考えてみてほしい。自動化して生成されたパッケージをメンテするだけでも大仕事だ。

そこまでやるのはしんどいので、バインディングを自動化して提供しない、という生き方もアリだ。React NativeやTitanium Mobileはそうやってきた。同じJS/TSに根差すフレームワークでも、NativeScriptはバインディングを提供する途を選んだ。Kotlin/Nativeもそうだ(KotlinはたかだかC APIinteropしか自動化しないので、難易度は相対的には低い。といってもそれも簡単ではないけど)。

バインディング自動化機構: やっていき

バインディング ライブラリは、オリジナルのライブラリがもつ階層的な依存関係を反映した階層的な依存関係をもつことになる。そうしないことも可能ではあるけど、依存関係はたいがい問題が生じないようにオリジナル側で慎重に設計されていることが多い。バインディングでこれをそのまま反映しないと、ユーザーがオリジナルと同様の依存関係に基づいてアプリケーションを構築できなくなるケースが出てきてしまう。必然的に、プラットフォームAPIバインディングも、サードパーティ ライブラリのバインディングも、同じ俎上に載せることになるだろう。

最初から全てを用意しない場合、プラットフォームAPIを呼び出す口は大概どのフレームワークにもあるので、ネイティブAPIを呼び出したいという需要にどれだけ答えられるかは、「使いたいものが最初から(あるいは既に)あるか」「どれだけ簡単に作れるか」にかかっている。最初から無くても、コミュニティが十分に大きければ、コミュニティの誰かがメンテしてくれるかもしれない。そのためには、ライブラリのエコシステムが機能していることが重要だ。gem、pip、npm、nuget、maven、cocoapods…

バインディングの自動化は、注文する側は「自動的に全部バインディングできるようにしてほしい」と気軽に言うだけだけど、そんな夢みたいなものが出てくることは有り得ない。言語間の相違を意識して、なるべくそれらのギャップを吸収できるようにしてやる必要がある。言語間の違いは、およそ開発者が思っている以上に大きい。better JavaC#からJavaを呼び出すのすら大変なのだ。ジェネリクス情報とか消えて無くなってるし。逆向きなんて無理ゲーにもほどがある。

無理ゲーなので、何をサポートして何をサポートしないか、サポートしない場合にどんな回避策やデメリットがあるか、といった事項を細かく気づいて対応できることが望ましい。設計重要。この点e4kはいささか危うい。設計議論も無ければ意思決定も無いように見える。逆にKotlin/Nativeのcinteropを見てほしい。素晴らしい。問題になりそうなコーナーケースはだいたいここに集まっている。

Xamarin.Androidは吸い出したAPI定義に変更を加えてからバインディングを生成するアプローチを採った。Xamarin.iOSは逆で、完成形のコードのスタブを作って、それらにAttributeを付けることで、コード生成時のヒントをそこから提供できるようにした。Kotlin/Nativeではコードを追加したり、バインドするメンバーをツール引数で調整できるようにしたり(ツール引数は設定ファイルからも指定できる)、さらにサポートできないメソッドについても、対応コードを生成した上で「これはサポートできない」というエラーを投げるようにしている。「バインディングツールが期待通りに動かない」という問題は、「期待されたコードが生成されない」と「間違ったコードが生成される」に大別される。サポートされないコードの生成を無視するよりは、生成されたコードに埋め込んでおくほうが妥当だ。

e4kにとっての「入力」は.NETアセンブリだ。アセンブリの内容を自由にカスタマイズするものとしては、mono linkerがある。メンバーの追加は無理だが、削除はできる。e4kはlinkerを内部で呼び出すことはしていないので、e4kに食わせる前にアセンブリをリンクするのが現状では妥当な解ということになる。ツールが分離独立していたほうが良いのかどうかは、正直まだ判断できない。

ビルドツールとIDEによるサポート: 仕上げ

ツールチェインの機能が充足してきたら、そろそろIDEで省力化したい。省力化というのは具体的には

  • 一般的なパラメータ集合のテンプレート化、簡易指定オプションの追加、設定ダイアログなどの追加
  • IDEからのビルド指示(ツールの実行)

くらいだろうか。こういうのをIDEでサポートする場合は、間違いなくアドインで実現することになる。もしIDEの必須機能になることがあったとしても、内部的には必ずアドイン機構を使用して実装する。monodevelopC#エディタなんかがそれだ。C#エディタの無いmonodevelopなんて考えられないけど、IDEそのものはC#エディタが無くても動くし、アドインを無効にすればC#編集サポート機能が無くてもよい。

ただ、IDEサポートは本当に必要だと考えられる場合にのみやったほうがよい。ビルドツールの拡張が先だ。.NETなら(残念ながら)MSBuildAndroidならGradleの拡張によって、ビルドツールをプロジェクトファイルで記述したとおりに呼び出して実行できるようにする。この時、ツールチェイン側も頻繁にコマンドライン オプションの変更があると、追従するのが面倒になる。だからある程度落ち着いてから着手したほうがよい。

IDEAPIを用いた拡張の作り込みは、実のところあまり望ましいとは思えない。GUIで作業したほうが効率的なのであれば、単独で動作するツールを構築したほうがよい。IDE側にも、それなりにGUIを組み込んでホストする仕組みが用意されているので、常に単独で動かさなければならないのか、という心配はあまりしなくてもよい。

機能によっては「IDEでやらざるを得ない」場合もあるが、それは主としてIDEの操作と密着した要件がある場合だ(たとえば、編集中のコードエディタのテキストバッファにアクセスするとか。この辺もlanguage-server-protocolや.NETのomnisharpなどを使うと必須でもなくなってくるが)。バインディングツールの支援機能に、そのようなものが求められることはあまりない。あるとしたらIDEしかプロジェクトの依存関係を提供してくれないような場合だ(e4kはアセンブリの参照解決を自前でやっつけているのでそのような問題はない)。

なぜIDEAPIを使用しないほうが良いかというと、APIの安定性がアテにならないからである。さらに、IDEも含むプラットフォームの開発者としては、一般の開発者にはおよそIDEAPIに安定性を求めないでほしいという気持ちがある。IDEAPIの安定性を過剰に求めると、IDEの進化が止まり、またAPIの互換性を維持するために内部で無理のあるスパゲッティ実装がなされ、実装が不安定になる。変化を嫌うなら一生古いものを使い続けていたほうがよい。

セッションの主な対象者(最後に)

こんな感じで、最後はまとめにくい論考になってしまったので、当日どこに落としどころをもっていくか決め兼ねているのだけど、フレームワークを作る側…あるいはそれにちかいところ…でいろいろ考えて発表する人はDroidKaigiでもほとんどいないと思うので、そういう話に興味をもってくれそうな人にはぜひ参加していただきたいと思っている。もちろんセッション後にも会場をフラフラしている予定なので、e4kに限らず議論したいことがあれば声をかけていただければと思う。