urushi blog

プログラマでゲーマーのうるしが綴るメモ

.NET のコードを他言語に変換? Embeddinator-4000 を使ってみた。

結論

先に所感から記載しておくと Embeddinator-4000 は「まだ実用的ではない」ものという結論になりました。実用ベースのものをお探しの人は参考になるものではないという認識のもと記事を読んでいただけると助かります。

動機

遊びでアプリを作りたい。公開も将来的にはしていきたい。その際に iOS, Android 両方で共有のライブラリを用いたい。しかし、UI 等はこだわりを持っていい感じに作成したいので、Xamarin.Forms のようなものでは不十分だし、ネイティブの有用なライブラリをがつがつと用いて、UX を上げたい。このような時に使える何かツールはないか? そこで Twitter 上で質問したところ、返ってきた答えに、 Embeddinator-4000 というものがあったので、GitHub から手探りで試してみました。

Embeddinator-4000 とは?

Xamarin のサブプロジェクトの一つで、.NET のライブラリを多言語に変換してマルチプラットフォーム上で用れるようにしたものです。GitHub に概要が記載されています。

Embeddinator-4000 の技術

認識間違いがあると思いますがご容赦ください

マルチプラットフォームで使えるというと、気になってくるのが技術面です。見たところによると、どうやら C にコード変換をした後に、各プラットフォーム向けにバインディングをしていくようです。Java (Android 向け) には JNI (Java Native Interface) を用いてデータの受け渡しを行い、 Objective-C (iOS 向け) には単に C の関数コールで C コードの実行を行っています。要するに、各言語毎に細かい実装があるわけではなく、基本的には C に落とし込まれているという認識です。また、C の実行に際して、ランタイム環境等を通して、実行時の抽象化された部分を上手く処理しています。

使用方法

基本的に以下のページを追っていくだけで何とかなります。(または GitHub の GetStarted を参照)

Xamarin .NET Embedding

C# .NET のライブラリを作成して、その依存関係に NuGet から Embeddinator-4000 を追加して、コマンドを打てばコンパイルできる! といいたいところだったのですが、現状 Android 向けのコンパイルは v0.3.0 ではエラーになってしまうので、GitHub のページから Clone してきて、Visual Studio でバイナリを作成する必要があります。

NuGet で取得してきてたフォルダ構成は、Embeddinator-4000 のコンパイルにおける最小構成になっています。Visual Studioコンパイルしたさいも、Embeddinator-4000.exe や objcgen.exe 以外にもサポートファイルが幾つも存在しているので、そこを参考にして移動してください。

使用感

各言語向けにコンパイルした後、Android プロジェクトなら aar を、iOS プロジェクトなら .framework を依存関係に追加すれば使用でき、Android からはそのまま Java だけでなく Kotlin のコードからも参照でき、iOS なら、Obective-C の bridging header を利用して Swift のコードから使用することができます。

Javaコンパイルした場合、パッケージ名 (名前空間) が存在しているため、C# のクラス名と一対一対応します。一方で、Objective-C にはそれに該当するものが存在しないため、クラス名が一対一対応しません。

namespace Service
{
    public class ActionClass {  ...  }
}

上記のようなクラスの場合、Servicre_ActionClass のような名前になります。要するにパッケージが深くなっていく度に、クラス名がどんどん長くなっていくので、実際に使用する際は型エイリアス (Swift) のようなものを用いて、短くして使用したいところです。

制限

特に iOS (Objective-C) ではサポートされていない機能が多く、そこが非常に大きなネックになっています。特に一番自分が触っていて感じた壁として、ジェネリクスがあります。C# におけるジェネリクスObjective-C におけるジェネリクスの差異があり、その吸収をランタイム環境で行っているっぽいのですが、iOS におけるアプリの規約上、ランタイム環境 (要は動的にプログラムを処理する機能) はアプリに含まれてはいけないという規定があります。そのため、iOS においてランタイム環境を含める事ができず、ジェネリクス対応が難しくなってしまっています。(GitHub における該当 issue) 参考:Xamarin における Generics について

自分は試しにライブラリに CoreTweet を追加したら、エラーになってしまい変換ができませんでした。このライブラリ自体は Xamarin.Native で扱えるはずのライブラリなので、もしかしたらいけるかな?と思っていましたが、やっぱり駄目でした。Objective-C についてはコンパイラが成熟してくれるのを暫く待たないと、まともに運用はできなさそうです。(C で吐くモードもあるのでそれを使えばいいじゃない、と思われるかもしれないですが、ランタイム環境が入ってくると思われるので、おそらく規約違反になって終了です) ( ドキュメントには記載はありませんが、Swift のコードコンパイルもできるみたいです。が、これもランタイム環境を同梱する形式のようなので、macOS にしか対応していません)

結論

Android についてはまだ自分の用途的にはなんとかなる部分もあるが、それであれば Java で書けばいいので、今のところ、Embeddinator-4000 を使用する意味は薄い。 (AndroidWindows ワールドを対象にしている方には意味があると思う) 一方で荒削りの部分も多いので、今後に期待が持てるプロジェクトだと思います。(ちゃんとメンテされていくのかはちょっと不明だけど、DroidKaigi 2018 にてコミッターの方がこんなセッションされるようなので、多分安心?)

おまけ:次のアクション

ジェネリクスを回避する策を試してみて無理そうなら、Embeddinator-4000 以外の別の方法を考える。自分のターゲットとしては iOS, Android なので、候補としては J2ObjC を考えている。(以前試したことがあったけど、かなりメンテされていていい感じになってそうな雰囲気を感じる?)

記事にいいねがいっぱい付いたら、失敗したサンプルプロジェクト上げます。(ライセンス的にもんだいなさそうなら、ビルドした Embeddinator-4000.exe とかも上げる)