インサイドXamarin(2)

インサイドXamarin(2)

Xamarinの基盤となっている「Mono」と、C#コンパイラー「mcs」

2016年9月9日 改訂 (初版:2014/1/21)

Xamarinにおけるソフトウェアの基盤であるMonoを深く理解すれば、Xamarin製品の理解はもっと深まる。今回はMonoの成り立ちから、そのソフトウェア構成、C#コンパイラーの内容までを解説する。

榎本 温(@atsushieno
  • このエントリーをはてなブックマークに追加

 前回は「Xamarinが何を提供しているのか?」について説明した。

 Xamarinは、テクノロジーとしては.NET FrameworkとC#の上に成り立っているが、ソフトウェアの基盤はこれらの技術の独自実装であるMonoの上に成り立っている。Monoについての理解が深まれば、Xamarin製品の理解も深まることになろう。

 そこで今回と次回は、このMonoについて解説しよう。

  • * Xamarin 2.0のリリースに際して、「Mono」の名前が「Xamarin」になったと思われていることもあるようだが、そうではなく、Monoは従来のままのオープンソースプロジェクトであり、XamarinはMonoを主にモバイル開発製品に適用しているということになる。

Monoの歴史的な成り立ち

 MonoプロジェクトのリーダーであるMiguel de Icazaは、本来はLinuxに自由なデスクトップ環境を実現する目的で設立されたGNOMEプロジェクトの初期リーダーであり、Monoは、当初はLinuxデスクトップ環境向けのアプリケーション開発を容易にするために始められたプロジェクトだった。当時のMiguelは、GNUプロジェクトからフリーソフトウェア大賞で表彰されるほどのUNIXハッカーである一方、マイクロソフトでインターンに応募していたこともあるほどのマイクロソフト技術フリークで、COM技術に傾倒して、類似のコンポーネント技術であるCORBAを応用した「Bonobo」というソフトウェアを開発して、GNOME開発の促進を図った、という歴史もある(CORBAは大変煩雑だったので、このプロジェクトは長続きせず、やがてDBusに取って代わられた)。彼は.NET Frameworkが登場するやいなや、その興味をこれに移し、そしてGNOME開発をこれでできるようにしようと、当時、彼が設立していたXimian社で、Monoプロジェクトを立ち上げたのだった。

 GNOMEには、その初期から「Gtk+」というGUIツールキットが存在した。これは画像編集ソフトウェア「GIMP」の中で使われていたUIフレームワークを、より汎用的にする目的で独立させて発展させてきたもので、Cで書かれている。.NET Frameworkには、P/Invokeという、ネイティブライブラリの呼び出しを実現する機能が備わっており、.NETであればWindowsのライブラリ専用になるが、Linux上のMonoであれば、これは共有ライブラリの呼び出しとすることができる。このP/Invokeを活用して、Gtk+の膨大なAPIに対応する.NETのAPIを実現する「Gtk#」が開発された。Banshee、F-Spot、Tomboy、BeagleなどのGNOMEアプリケーションが、このGtk#によって開発されている。

 Monoは主に、さまざまなADO.NETプロバイダーや、ASP.NETなどサーバーサイドの技術を実装するところから始め、それがバージョン1.0のリリースで一段落つくと、次はWindows Forms(Windowsフォーム)の実装にかかった(当時、.NETのGUIフレームワークはWindows Formsだけだったので、必然的にMonoの実装を求める声が高まっていた)。それがバージョン2.0で、.NET 2.0までの実装を実現するころには、.NETは3.5をリリースしていた。次の画面の「paint-mono」は、MonoのWindows Formsで作成されている。

Paint.NETをmonoで動くようにした「paint-mono」(ちなみにLinuxならpintaの方がお勧め)
Paint.NETをmonoで動くようにした「paint-mono」(ちなみにLinuxならpintaの方がお勧め)

 .NET技術は広範で、Monoチームは、実装対象を絞り込む必要があった。主に、ランタイムとC#コンパイラーの改善、ASP.NET、WCFなどのサーバーサイド技術の実装などが、集中的に開発されることになった。また、これとは別に、Gtk#やMonoDevelopの開発も並行して行われていた。幸いなことに、Windowsフォームは、WPFの登場を境に、本家.NETでも開発がほぼ止まった。

 「アプリケーションをシームレスにLinuxでも動作させる」というレベルの目的でも、.NET Frameworkにキャッチアップするのは困難な課題であるように思われたが、Monoチームがコア部分を着実に進化させる中、1つの転換点が訪れる。ASP.NET MVCを皮切りに、外部公開されたマイクロソフト社製オープンソースプロジェクトを.NET Frameworkに取り込む流れが、マイクロソフトで形成されつつあったのだ。最終的に、.NET Framework 4.0には、すでにオープンソースで公開されていた「ASP.NET MVC」と「DLR (Dynamic Language Runtime)」が含まれることになった。これらオープンソースの成果は、Monoにもそのまま取り込むことができた(きちんと動かすためにはバグフィックスを必要としているが)。

 その後もマイクロソフトは、「Entity Framework」、「Reactive Extensions」といった.NETの大規模ライブラリを次々にオープンソースにしていった。これらはMonoにもそのまま取り込まれてバイナリ配布されている。

 一方、コア部分に集中できたMonoチームは、それらの完成度を高めつつ、対象プラットフォームを拡大できた。C#コンパイラーは、いち早くC# 5.0の諸機能を実装できた。iOSとAndroidは、Xamarin製品の両輪となっているが、これらをターゲットとするために、ランタイムが改善されている側面も大きい(次回解説するが、世代別ガベージコレクションなどがそうだ)。

.NET Frameworkのライブラリのオープンソース化と、それ以降の流れ

 2014年に、マイクロソフトは方向性の大転換を行い、.NET Frameworkライブラリのかなりの部分のソースコードを公開し、さらに.NET Coreと呼ばれるクロスプラットフォームのフレームワークの開発をアナウンスした。

 .NET Frameworkのライブラリはreferencesourceという名前で、GitHub上のMicrosoft organizationから公開され、そこにはサーバーサイドの.NET Frameworkの機能を実現するコードを中心に、現在使用されているソースが、(恐らく)ほぼそのまま公開されている。referencesourceは、あくまで既に公開されているWindows上でしか動作しない.NET Frameworkのソースであり、Monoでそのまま使えるわけでもなければ、そもそもビルドできるソースを公開しているわけでもない。reference~というモジュール名には相応の意味がある。

 .NET Coreとは別にreferencesourceが公開されたのは、まさにMonoで取り込まれることを意図したものである(.NETチームはそのように解説している)。Monoは.NET Frameworkに相当する機能を数多く実装してきたが、あくまでフルスクラッチの実装であり、実装の詳細は常に.NET Frameworkと異なる部分が存在していた。アプリケーション開発者も、APIドキュメントに記載された動作にのみ基づいてこれらのAPIを使用してきたわけではなかったので、何らかのコードが.NET Framework上でしか(あるいはMono上でしか)動作しない可能性は常にあった。

 Monoでは、このreferencesourceを積極的に取り込む作業が行われることになり、数多くのライブラリがこのreferencesourceによる実装に置き換えられた。ライブラリには、特にmscorlib.dllにはランタイムと密結合している部分が多く存在するが、それらも含めて、現在でも着実にreferencesourceに置き換えられている。密結合部分を置き換えるためには、ランタイム自体に手を加えている。

Monoのソフトウェア構成要素

 今回ここで説明する“mono”とは、「mono」というソフトウェアリポジトリにソースコード(以下、「ソース」と表記)が含まれるソフトウェア一式を指している。このリポジトリには、ランタイムC#コンパイラーmonoクラスライブラリの3つも含まれている。monoの公開リポジトリは、GitHub上にあるmono organizationにより提供している。

  • * monoをWindows以外のデスクトップで使用するためには、monoとは別に「libgdiplus」というライブラリをビルドすることも指示されるが、これはWindowsにおける「GDI+」というプラットフォームのネイティブAPIをWindows以外のOS用に実装するもので、具体的にはライブラリSystem.Drawing.dllが使用している。System.Drawing.dllは、Windows Formsの中心となるSystem.Windows.Forms.dllの描画部分の基盤となるもので、Windows Formsを使う場合のみ必須だ。そうでなければ、ほぼ無視しても構わない。

 monoのソース上で、先の3つの構成要素(ランタイム、C#コンパイラー、クラスライブラリ)は、それぞれ次のディレクトリに存在する。

  mono/ # トップディレクトリ
   ├ mono # ランタイム
   └ mcs/
     ├ mcs # コンパイラー
     └ class # クラスライブラリ

 それぞれの内容については、以降で掘り下げて説明する。

monoのビルド

 ここでは、monoのビルドについてごく簡単に説明する。

 なお、最新版などを自前でビルドする必要がなければ、単にパッケージをインストールするだけで足りるはずだ。Monoのサイトでは、各種Linuxディストリビューション、Mac、Windowsについて、インストール可能なパッケージを用意している。

 monoのソースは、典型的なUNIXツールと同じく、MacおよびLinuxなら、

Bash
./configure # tarballからのビルドでなければ ./autogen.sh
make
make install
monoのソースをインストールする際のコマンド実行手順

という流れでインストールできる(configureのオプションについては--helpオプションを付けた実行結果を参照されたい)。ただし、Mac OS Xの場合、Xcodeのツールチェーンを前提としているので、macportsやhomebrewを使っている場合は、それらを無効にした上でビルドする必要がある。他のUNIX系OSは、公式にはサポートされていないが、問題があれば、メーリングリストなどで報告してもらえれば、各プラットフォームのハッカーがパッチを作ってくれるかもしれない。FreeBSDビルドは日本のハッカーがメンテナンスしていたこともある。

 Windowsの場合も、全てビルドしようと思ったら、cygwin上でmakeベースのビルドを実行することになるが、いくつかcygwinのパッケージが追加で必要になる(公式サイト参照)。ランタイムのみをビルドする場合は、ソースツリーのmono/msvcディレクトリmono.slnというVisual Studioのソリューションファイルが使用できる。クラスライブラリにもclasslibs.slnというソリューションファイルが用意されている(ただ、巨大なソリューションなので、実用されているかは疑問だ)。

 monoランタイムはCで書かれている。ビルドされるコードの依存ライブラリは、現在では(g)libcくらいしかない(そこからiconvなども使っているので、cygwinでmingwを使用してビルドするときにはインストールが必要になる)。内部的にはglibのAPIを多用しているが、glibの実装に相当する「eglib」というライブラリが、monoの一部として存在していて、現在ではこれが使われている(組み込みライセンスを提供するに当たって、glibのライセンスに拘束されないよう、自前で実装している)。

 コンパイラーおよびクラスライブラリは、C#で書かれている。実のところ、mscorlib.dll以外は、無理やりハックすれば、Windowsのcscでもビルドできる(そのように実装する)し、C#コンパイラーmcsは、セルフホストできるようになるまでは、Visual Studioおよびcscで開発されていた。ただ、ほとんどのmonoハッカーは、monoのクラスライブラリをIDEで開発していない(と思われる。前述のclasslib.slnは、巨大すぎて実用性に欠ける)。主に.dll.sourcesという拡張子のファイルにソースファイル名を列挙して、makeでビルドしている。もしクラスライブラリをIDEでハックしたい場合は、テキストエディタで編集するか、あるいはソースツリー中の.csprojファイルを何とか加工したり組み合わせたりする必要があろう(実際、筆者は最近Microsoft.Build.dllを実装するために、独自のソリューションファイルを作成して、MonoDevelopでコーディングしている)。

 それぞれのサブディレクトリでmakeを実行すれば、関連する部分だけをビルドできるので、monoをハックする場合には、そうした方が仕事が早く済むだろう。System.Xml.dllのバグフィックスのために、mono全体をビルドし直す必要は全く無い。もっとも、クラスライブラリやコンパイラーには、(まだ詳しく説明しないが)フレームワークバージョンを表すプロファイルが複数存在するので、最終的な修正ができたら、一度、トップレベルからビルドしてみた方がよいだろう。

mcs: C#コンパイラー

 さて、ここからmonoの各構成要素を掘り下げて説明する。まず説明が簡単なC#コンパイラーmcsから説明する(以降、単にmcs)。mcsはC#で実装されていて、初期はcscでビルドされていた(mcsのビルドにはmcsが必要になるので、現在でもブートストラップ用のビルド済みmcs.exeがまず使用される)。

mcsとC#仕様の実装状況

 最新のmcsは、asyncawaitキーワード、呼び出し元情報(caller info)などを含む、C# 6.0までの機能を全て実装している。cscとほぼ同様のコマンドラインオプションを備え、既存のC#コードを問題なくビルドできる。さらに、文法エラーチェックも、.NET Frameworkのcsc並に厳格に行ってくれる(その背景には、C#の言語仕様が規定するC#コードのフロー解析と、mcsにおける忠実な実装がある)。cscのエラーと警告は、Visual Studioと親和的に動作するために、一定の書式で出力されるが、mcsでもそれは踏襲されていて、MonoDevelopでも同様にメッセージがリストで表示されるようになっている。Windows上で動作するMonoDevelopはcscを使用しているが、そのコンパイルエラーはMonoDevelopで正しくリスト表示されるはずだ。

 ECMA 334およびECMA 335という仕様は、C#のソースレベルの互換性とCIL(バイナリ中間コード)のいずれについても互換性を持たせるためのものであって、mcsが生成する.exeファイルおよび.dllファイルは、cscが生成するものと互換性がある。対象となるプラットフォームで読み込めるアセンブリであれば、mcsが生成したものを.NET Frameworkで読み込んでも、cscが生成したものをmonoで読み込んでも、それぞれ実行できるはずである(もちろん、対応するプロファイルの有無、および対象プロファイルにおける参照アセンブリの有無は、ここでは別の問題だ)。

 ただし、mcs-debugオプションで生成するデバッグシンボルファイルは「.mdb」(MDB)という独自のフォーマットになっている。これはECMA CLIにデバッグシンボルの規定が無く、.pdbファイルの仕様(PDBフォーマット)が非公開だったためだ(現在では、CILDBという、PDBでもMDBでもないフォーマットが規定されている)。さらに.pdbファイルの仕様には、Windows依存の部分があったこともあり、結局のところ、オープンソース化された.NET CoreやRoslynでは、新たにPortale PDB(PPDB)という仕様をサポートすることになった。MonoでもこのPPDBをサポートする作業が行われている。

 これとは別に、.pdbを.mdbに変換するpdb2mdbというツールが存在している。ちなみに、これらはフォーマットの違いでしかなく、情報の性質に違いは無いようだ

 mcsによるLINQdynamicのサポートの条件は、cscの場合とあまり変わらない。すなわち、拡張メソッドを使うためにはSystem.Core.dllを参照に加えた上でusing System.Linq;と記述しなければならず、dynamic型を使うためにはMicrosoft.CSharp.dllを参照に加えなければならない(これは間接的にMono.CSharp.dllというライブラリも利用する)。逆に、これらのライブラリさえあれば、mcsdynamic型を十分にサポートできる(筆者は実際に、PlayStation Mobileがまだ「PSSuite」と呼ばれていたころに、同環境でこれらのアセンブリを適切なプロファイルでビルドして追加し、サポートされていないはずのdynamic型を記述しているコードを動かしたことがある)。

 ちなみに、mcscscと異なり、多数のライブラリをデフォルトで参照しない。csc.rspが無いような状態だと考えるとよい。これは.NET開発者にとっては多少の混乱の元ではあるが、使われないアセンブリを無駄に解析すれば、それだけコンパイル時間が犠牲になる。それを嫌がってのことである。

mcsの設計

 mcsは、ソースの解析(パーサー)、セマンティック分析、コード生成といった、一般的な手順に基づく言語コンパイラーの実装なので、特筆すべき事柄は多くない。ソースの解析には、jayというLALR(1)文法のパーサージェネレーターを使っている(もともとはJavaのソースを生成するツールだが、C#生成機能を追加した)。

 コード生成は、かつては.NETのmscorlibアセンブリに含まれているSystem.Reflection.Emit名前空間を使用していたが、これは「クロスプロファイル」な形でアセンブリをビルドするのを妨げるものだったので(例えば.NET 4.0互換のアセンブリを生成するには、.NET 4.0のプロファイルのmscorlib.dllで動作するmcsが必要だった)、現在では「IKVM.Reflection.Emit」というライブラリを使っている。IKVMは、Javaバイトコードを.NET/Mono上で動かすというプロジェクトだ。もしあなたが言語処理系を作成しているなら、System.Reflection.Emit名前空間の代わりにIKVM.Reflection.Emitライブラリの使用を検討した方が、後々楽になるかもしれない。System.Reflection.Emitを使っていたころは、monoにはターゲットプロファイルに合わせて、mcsgmcsdmcssmcsといったコンパイラーツールが乱立していた。今はmcsのオプション-sdkで全て対応している。

 セマンティック分析は、mcsの一番複雑な部分だが、yieldキーワードや匿名型、varによる型推論、dynamic型の使用、asyncawaitキーワードのTask処理などが、どういうロジックとして生成されているか、もし興味があれば、mcsのソースを追うことで調べられる。

Roslynとの関係

 mcsは、長らく単体のツールとして存在してきたが、00年代後半から、Mono.CSharp.dllという独立したライブラリとして再利用できるようになっている。また、MonoDevelopとSharpDevelopの両プロジェクト間で、C#の「サービスとしてのコンパイラー」(Compiler as a Service、またはLanguage Service)として開発されている「NRefactory」でも使われてきた。

 一方、マイクロソフトは、2014年に彼らの新しいC#コンパイラーであるRoslynをオープンソースで公開した。これは単なるコンパイラーではなく、IDEに統合して、コード補完やシンボル利用箇所の検索などにも活用できるようなコンパイラー・インフラストラクチャである。機能的には、Monoプロジェクトがmcsを使用して発展させてきたエコシステムと重複する部分が多い。

 Monoチームでは、既に完成度が高くパフォーマンスも良かったmcsを、あえて開発途上で粗削りの実装だったRoslynに置き換えることはせず、まずはコンパイラー・インフラストラクチャとしてmcsをベースに実装されていたNRefactoryというコンポーネントを、Roslynで置き換える作業から着手した。MonoDevelop 6.0では、NRefactoryはRoslynで置き換えられている(ただし、Roslynには存在しなかった機能などもあり、それらはRefactoringEssentialsという、Roslynの内部メソッドを公開APIにするようなライブラリで実現している)。

 Roslynをmonoのディストリビューションに含めてmcsを置き換えていくための作業は、2016年になって、ようやく具体的な作業が計画され始めた段階だ。2016年6月の時点では、mcsにC# 7の機能を実装する予定は無く、Roslynを取り込むことで実現されることになると思われる(将来の話であり、計画の変更はあり得る)。

C#シェル

 最後に、mcsの興味深い応用として、mono C# shellを紹介しよう。これはcsharp.exeというツールで、中ではこのMono.CSharp.dllを使用して、C#でインタラクティブシェルを実現するものだ。Gtk#のツールがコンソールから起動できる環境(Linuxなど)なら、gsharpというGUIシェルも利用できる(次の画面を参照)。

gsharp

 C#のコードをその場で書いて、Gtk#のWidgetを作って表示することも可能だ(Miguelはかつてこの上にGtk.Canvasで図形を描画するデモを行っていた)。MonoDevelopのユーザーコミュニティでは、このmono C# shellをMonoDevelopのパッドとして使えるようにしたアドインも公開されている。インタラクティブシェルはもはやF#(fsi)の独壇場ではなくなった。

 次回はmonoランタイムと、Monoのクラスライブラリについて解説する。

※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

1. Xamarinを構成するソフトウェア。その主要な10要素とは?

Xamarinは何を提供しているのか? その主要なソフトウェア構成要素として、Mono、Gtk#、MonoDevelopとXamarin Studio、Xamarin.iOS、Xamarin.Android、Xamarin.Mac、Visual Studioアドイン、Xamarin.Forms、Xamarinコンポーネント、Xamarin Test Cloudなどについて紹介。

2. 【現在、表示中】≫ Xamarinの基盤となっている「Mono」と、C#コンパイラー「mcs」

Xamarinにおけるソフトウェアの基盤であるMonoを深く理解すれば、Xamarin製品の理解はもっと深まる。今回はMonoの成り立ちから、そのソフトウェア構成、C#コンパイラーの内容までを解説する。

3. Xamarinの基盤「Mono」のmonoランタイムとクラスライブラリ

Xamarinにおけるソフトウェアの基盤であるMonoを深く理解すれば、Xamarin製品の理解はもっと深まる。今回はmonoランタイムと、Monoのクラスライブラリについて解説する。

4. Monoのモバイル化の流れ ― Xamarin.iOS/Xamarin.Androidの誕生

デスクトップ環境での動作を主眼に開発された「.NET」のオープンソース実装である「Mono」は、どのようにモバイル開発に向かって流れていくことになったのか。

5. Xamarin.iOSの仕組みとアプリケーションの構成

いよいよXamarin.iOSを取り上げる。その仕組みや、Xamarin.iOSアプリの作成/ビルド/実行とデバッグなどについて解説。

イベント情報(メディアスポンサーです)

Twitterでつぶやこう!


Build Insider賛同企業・団体

Build Insiderは、以下の企業・団体の支援を受けて活動しています(募集概要)。

ゴールドレベル

  • グレープシティ株式会社
  • 日本マイクロソフト株式会社