はじめに
第1章 Embeddinator-4000の設計と実装
第2章 Xamarin.Macアプリケーションの配布方法
第3章 Plugins for Xamarin & Unit Test
第4章 MonkeyFest2017 参加レポート
第5章 世界を広げるMicrosoft Cognitive Services
第6章 IL2Cプロジェクト
著者紹介
Extensive Xamarinは、前著「Essential Xamarin」に続くクロスプラットフォーム開発環境Xamarinについての解説書です。前作がXamarinの最新情報をもとに基本を押さえる内容でしたが、2018年1月現在Xamarin関係の商業書籍が増えてきた中、本書では前作を踏まえて「総論ではない」「一歩先の」「深い」話をまとめています。
本書には、Xamarinが「外側に出ていく」ための記事を収録しています。.NETのコードをJavaやObjective-Cのプロジェクトで使用する「Embeddinator-4000」の解説、Xamarin.Macアプリケーションを作成した後に誰もが悩むであろうその配布方法、クロスプラットフォーム開発の可能性を拡大する「Plugins for Xamarin」の基本やDIコンテナを用いたユニットテスト手法、「Microsoft Cognitive Service」にXamarinを繫げるための道筋、そして変わり種として海外のXamarinカンファレンスに出てセッショントークをこなすためのさまざまなTipsが、この1冊にまとめられています。
本書の読者のみなさんが、Xamarinの技術やXamarin技術者の可能性の広がりを感じとることができれば、さらには自ら実践までできるようになれば幸いです。
執筆者代表 榎本 温
本書に記載された内容は、情報の提供のみを目的としています。したがって、本書を用いた開発、製作、運用は、必ずご自身の責任と判断によって行ってください。これらの情報による開発、製作、運用の結果について、著者らはいかなる責任も負いません。
本書は技術系同人誌即売会「技術書典3」で頒布された『Extensive Xamarin』の内容をもとに加筆・修正を行ったものです。
「Embeddinator-4000」は、Mono上で動作するライブラリやXamarinで作成されたライブラリを、各プラットフォームで使用できるライブラリとしてパッケージできるようにする、相互運用のための技術です。アプリケーションは全体としてはプラットフォームネイティブで作成しつつ、必要に応じてMonoやXamarinで実装した機能を「呼び出す」ことができるようになります。
(なお、本稿では特に省略せずにEmbeddinator-4000と記述しますが、「e4k」などと表記されることもあります。)
Xamarinの世界では、特に2017年になって「ネイティブのコードを使う」ための技術がいくつか登場し、混乱を招きやすい状態になっているので、少し整理しましょう。
1) Native Embedding(Xamarin.Forms)
これは、Xamarinネイティブ(プラットフォーム固有)のコントロールをXamarin.FormsのLayoutの子として追加できるようにした技術です。これによって、Xamarin.Formsの共通コントロールをPCLとして(bait and switchのような技術を使用して)パッケージしなくても、直接プラットフォーム固有のコントロールとして埋め込めることになります。
2) Forms Embedding(Xamarin.Forms)
これは、Xamarin.FormsのページをXamarinネイティブ(プラットフォーム固有)のUIコントロールとして追加できるようにした技術です。ちょうどNative Embeddingとは逆向きの機能だといえます。Xamarin.Formsで書いてしまったビューがあるけど、どうしてもXamarinネイティブのプロジェクトで使いたい、という場合に、逃げ道として採用できる技術です。
3) Embeddinator-4000 (.NET Embedding)
本章で論じる技術はこちらで、Xamarinプラットフォーム用に書かれたコードを、プラットフォームネイティブ言語のライブラリとして利用できるようにするものです。Xamarin.Formsと直接の関係はありませんが、Xamarin.FormsはXamarinプラットフォーム上で動作するライブラリであり、基本的にはEmbeddinatorの対象となるものです(ただし2017年11月の時点で公開されているバージョン0.3.0ではPCLの参照解決はうまくできていません)。
2017年11月に行われたMicrosoft Connect(); 2017ではEmbeddinator-4000のことを「.NET Embedding」という馴染みのない表現で紹介されていましたが、これがどれくらい通用するかはまだ分からず、この混乱にさらに拍車をかけそうな気もするので、本稿ではEmbeddinator-4000の呼称を用いることにします。
.NETが誕生し発展してきたWindowsの世界ではあまり一般的ではありませんが、ネイティブコードに変換しないタイプのプログラミング言語とその前提となる実行環境では、その上で動作するアプリケーションをネイティブアプリケーションとしてパッケージして配布する仕組みが、しばしば用意されています。
これらの全てではありませんが、このようなパッケージ化の技術の多くは「実行環境をアプリケーションに同梱して配布する」仕組みになっています。実行環境がプロプラエタリーで自由に配布できない場合(ここでは「ランタイムが自由なソフトウェアで、かつ実行時にプロセスがランタイムと一体になるために、自由なソフトウェアとして配布されることを避けたい場合」なども含みます)は別として、一般的にはこのアプローチがもっともシンプルです。
Monoの世界には、mkbundleというツールが存在しており、monoランタイムとmscorlib.dllなどのフレームワークアセンブリ、アプリケーションのアセンブリをまとめて、実行時にこれらを展開してロードする仕組みが以前から備わっていました。つまり、表層的には、.NETアプリケーションをネイティブアプリケーションであるかのように振る舞わせることが可能だったのです。
また、iOSに関しては、Xamarin.iOSはマネージドコードをAOT(事前)コンパイルによってネイティブコードに変換して実行するものであり、これはマネージドコードをネイティブライブラリそのものに変換する技術です。(この点では、同じObjective-Cを基盤とする技術でも、Xamarin.iOSとXamarin.Macは大きく異なりますが、特にデスクトップ・フレームワークで動作するXamarin.MacはMonoのデスクトップアプリケーションと同じであり、特別に難しいことは何もありません。)
Embeddinator-4000の場合、この観点では、Mono/Xamarin向けライブラリは、ネイティブライブラリとしてmonoランタイムと一緒にパッケージされて配布できるようになったものである、といえます。
もちろん、単にmonoランタイムとマネージドコードのライブラリをバンドルするだけでは、そのまま呼び出せるかたちになりません。ネイティブプラットフォーム言語向けのAPIも用意する、というのがEmbeddinator-4000の重要な仕事です。
当初、Embeddinator-4000はMonoランタイムで動作するデスクトップアプリケーションのみを前提として開発されていましたが、2017年8月の時点でAndroid用のJava APIとiOS/macOS用のObjective-C APIも自動生成できるようになっています。さらに11月の開発中のバージョンではSwiftのコードも生成できるようになっています。ただし、まだプレビュー段階であり、また正式に「使える」ものとされているのはAndroid用のJavaのみです。生成されるAPIおよび実装については、以降の節でもう少し詳しく見ていきます。
2017年11月の時点では、Embeddinator-4000はコンソール・ツールであり、ユーザーがコマンドラインから実行して使用することが前提になっています。入手経路は主にふたつあります。
1.NuGetでパッケージをインストールする
こちらがもっとも一般的な方法でしょう。Visual Studio(for Windows / for Mac)などでプロジェクトを開き、NuGet packagesのダイアログを開いて "Embeddinator" を検索すると出てくるはずです。これを追加して、NuGetパッケージのダウンロードとインストールが成功すると、ソリューションのpackagesディレクトリにEmbeddinator-4000.0.3.0 のようなサブディレクトリが作成されているはずです。これがパッケージの内容を全て含んでおり、tools/MonoEmbeddinator4000.exe などが実行すべきツールとなります。
本稿執筆時点での最新バージョンは0.3.0です。
2.https://github.com/mono/Embeddinator-4000 をチェックアウトしてビルドする
Embeddinator-4000はオープンソースであり、ソースからビルドして使うこともできます。ソースをチェックアウトして build.sh を実行するだけで、(ビルドが成功すれば) build/lib/Release/MonoEmbeddinator4000.exe などがビルドされます。ビルドにはCakeが必要で、内部的にはさらにdotnetコマンド(dotnet/cli)を使用しているので、これらが動作することが前提です。
なお、xamarin-androidと組み合わせて、Linux上でも使うことが可能であるのが理想ですが、xamarin-androidが内部的に使用している xamarin-android-tools というリポジトリのコードが Embeddinator-4000 で使用しているものと適合しておらず、古い製品版Xamarin.Androidのセットアップが必要になっているのが2017年11月時点での現状です(つまりLinux環境ではまだ修正を加えないと使えません)。
githubのソースは日々開発者がチェックインしているものであり、ビルドしても通らないこともしばしばあります(筆者も本章の初版をまとめていたときは最新のmasterがビルドせずに難儀したものでした)。
コマンドラインツールを実行してライブラリをビルドします。このとき、実は言語によって実行するツールが異なります。CおよびJavaではMonoEmbeddinator4000.exe、Objective-Cではobjcgen.exeとなります。それぞれ次のようなコマンドを実行します。パスはNuGetパッケージでインストールした場合を想定しているので、ソースからビルドした場合は適宜変更してください。
mono packages/Embeddinator-4000.0.2.0.80/tools/Embeddinator-4000.exe
-platform=Linux -gen=c -c project/bin/Debug/MyLibrary.dll
mono packages/Embeddinator-4000.0.2.0.80/tools/Embeddinator-4000.exe
-platform=Android -gen=Java -c project/bin/Debug/MyLibrary.dll
mono packages/Embeddinator-4000.0.2.0.80/tools/objcgen.exe
-platform=macOS -gen=Obj-C -c project/bin/Debug/MyLibrary.dll
Objective-Cのobjcgen.exeの例ではplatform引数にmacOSを指定しましたが、iOSを指定することもできます。
なお、 オプション-cはコンパイラを呼び出してライブラリをビルドするところまで行うためのものですが、Cコンパイラの呼び出しには、WindowsではMSVC、Macではclang、Linuxではgccを利用します。ちなみに、Androidについては、Android NDKを使用してネイティブライブラリをビルドしているので、Linux上で-cを指定しても正しく動作するはずです(ただし、前述の理由により、まだ利用できません)。
Embeddinator-4000のライブラリ生成処理が完了すると、対象プラットフォームに対応したAPIを含むライブラリのソースが生成されます。コンパイルも行うようにしていれば、ビルド処理も行われ、Androidならaar、macOSならframeworkやdylib、といった、各プラットフォームに対応するライブラリが出力されます。
ここではCのAPIを具体的なコードで生成してみましょう。まず次のようなC#ソースを用意します。
public class MyLibrary
{
public string Hello (string input)
{
return "Hello, " + input;
}
}
極めて単純なHelloメソッドを含むライブラリです。これをcscでコンパイルします。
$ csc -t:library MyLibrary.cs
このDLLからCのAPIを生成します。-p Windows と指定している部分はとりあえずおまじないと思っていても大丈夫です。(プラットフォーム指定オプションは必須なのにLinuxなどが指定できないため、こう指定しています。)
$ mono --debug /.../MonoEmbeddinator4000.exe --gen=C -p Windows MyLibrary.dll
Parsing assemblies...
Parsed 'MyLibrary.dll'
Processing assemblies...
Generating binding code...
Generated: MyLibrary.h
Generated: MyLibrary.c
Generated: c-support.c
Generated: c-support.h
Generated: embeddinator.h
Generated: glib.c
Generated: glib.h
Generated: mono-support.c
Generated: mono-support.h
Generated: mono_embeddinator.c
Generated: mono_embeddinator.h
ここでは-cオプションを指定していないため、生成したCソースをコンパイルする作業は行われていません。生成されたMyLibrary.hの内容を抜粋します。(紙面に合わせて適宜改行しています)
MONO_EMBEDDINATOR_BEGIN_DECLS
typedef MonoEmbedObject MyLibrary;
MONO_EMBEDDINATOR_API MyLibrary* MyLibrary_new();
MONO_EMBEDDINATOR_API const char* MyLibrary_Hello(MyLibrary* object, \
const char* input);
MONO_EMBEDDINATOR_END_DECLS
MyLibraryオブジェクトを生成するMyLibrary_new()、そのHelloメソッドを呼び出すMyLibrary_Hello()が定義されていることが読み取れます。FooBar_new()やFooBar_instance_method()といった関数は、C言語でよくあるオブジェクト指向のスタイルです。MyLibrary.cにはこの実装が含まれていますが、内容は割愛します。
実際にこのAPIを使ってみましょう。次のようなCのコードを作成します。
#include <stdio.h>
#include <MyLibrary.h>
void main (int argc, char **argv)
{
MyLibrary *obj = MyLibrary_new();
puts (MyLibrary_Hello(obj, argv[1]));
mono_embeddinator_destroy_object(obj);
}
先のヘッダファイルにmono_embeddinator_destroy_object()は出てきませんでしたが、これは自動生成される(というよりコピー出力される)mono_embeddinator.hで定義されています。newに対するdeleteと考えてよいです。
このソースを含む全体をコンパイルしてみましょう(通常はいったんEmbeddinator-4000に-cオプションを指定してlibMyLibrary.aなどをビルドして使いますが、今回は-cが使えない環境でも利用できることを示すため、ソースから全部まとめてビルドします)。コンパイラはgccでもclangでもその他何でもかまいませんが、monoの開発用パッケージのファイルを使うので少々特殊な引数(pkg-configの実行結果など)が必要です。
$ gcc -I . *.c `pkg-config --cflags mono-2` `pkg-config --libs mono-2` -o app
これでappという実行可能ファイルが生成されました。実行してみましょう。
$ ./app Hogehoge
Hello, Hogehoge
このように出力されれば成功です。
iOSやAndroidの場合も、これらをソースからビルドすることは可能ですが、Embeddinator-4000がビルドするライブラリを使用したほうが簡単でしょう。