インサイドXamarin(13)
Xamarinと、ポータブル・クラス・ライブラリ(PCL)
複数プラットフォーム向けのライブラリを作れるPCLの概要と利点について解説。また、Xamarin.iOSやXamarin.Androidでの利用方法や、XamarinでPCLを実現する仕組み、PCLの課題を説明する。
今回はPCL(ポータブル・クラス・ライブラリ)について取り上げる。
ポータブル・クラス・ライブラリの意義
PCLについてはMSDNに詳しい記事があり、あらためてここで説明するようなことでもないが、PCLの「P」とは「portable」であり、日本語にすれば「可搬性がある」「移植性がある」というニュアンスになるだろう。.NETにいくつかのプロファイルが存在することは、第4回でも言及したが、ここで言われている「移植」とは、「.NET技術に準拠する各種プロファイルの2つ以上で利用できること」を示すと考えればよい。
- * 「.NET」という語句も、特にSilverlightなどが含まれるこのPCLの文脈においては、実のところあまり正確な定義を伴わないが、今回は乱暴に「C#などで作成されたプログラムが動作する、ECMA CLIおよび類似環境」とでもしておきたい。
PCLとして作成されたライブラリは、複数のプラットフォームで使うことができる。同一のコードを使い回せるので、コードのメンテナンスが楽になるかもしれない。ただし、使用できるAPIは、全ての対象プラットフォームで使用できるものに限定される。これは基本的に.NET FrameworkのAPIのサブセットであって、特定のプラットフォームでしか利用できない「ポータブルでない」APIは除外される。PCLをどう使いこなすかは、開発者の考え方次第だろう。
PCLの主な活用方法としては、アプリケーションにおけるビジネスロジックに含まれる部分をPCLで実装して、クロスプラットフォームで共有する、といった開発手法が挙げられる。.NET開発では、MVVMパターンが主にWPFを対象としたアプリケーション開発モデルとして推奨されているが、Xamarinをターゲットとする場合でもMVVMを適用することは可能であり、実際にMvvmCrossなどのライブラリで適用されている。MVVMにおいては、プラットフォームに依存しないModelおよびViewModelに相当する部分を、PCLに基づいて作成できるだろう(MvvmCrossの使い方については、Microsoft渡辺氏のはてなブログで詳細にまとめられている)。
PCLは、複数プラットフォームで共通のAPIを集めているが、単一の最大公約数的なAPIではない。開発者が自分のPCLでどのプラットフォームを対象とするか決定することで、利用できるAPIの範囲が決まる。PCLには「プロファイル」があり、各プロファイルが対象プラットフォームの集合体を規定している。
PCLとは何だったのか
PCLにおける「プロファイル」は、第4回で言及したものとは幾分か異なる分類のものが含まれている。PCLのプロファイルは、.NET Frameworkの機能全てを選ぶことも、そのサブセットを選ぶこともある。.NET Framework Client Profileのような名前を見たことがある人も多いのではないだろうか。
さらに、伝統的には、PCLが前提としてきた「複数プラットフォーム」とは、実のところSilverlightが動作するMac OS Xを除けば、全てマイクロソフトのOS(Windows、Windows RT、Windows Phone、Xbox)である。JavaやQtやWxWidgetの言うような「クロスプラットフォーム」には遠く及ばない。このPCLにおける「クロスプラットフォームを意識したコード」は、例えばUIレイヤーにおいては、しょせん「Silverlightでも動作する」程度の存在でしかなかった。デスクトップの.NETもSilverlightも、UI層のAPIはWPFを基にしており、根本的な違いは無い。WPFはMonoには存在しないため、それらUIの「ポータブルなクラスライブラリ」には、Mono環境への移植性は全くなかった。Mono開発者の視点で言えば、Monoで利用できないコードでは、到底ポータブルとは言えず、PCLの仕組みをサポートする意義は小さかった。
そのような状況であったが、2013年後半になってPCLの位置付けはだいぶ変わる。マイクロソフトがPCLのアセンブリをMonoプロジェクトでも利用できるように無償ライセンスで公開したのである。さらに、Xamarin.iOSやXamarin.Androidがターゲットとして追加された。両製品ともWindows Phoneに類似するAPIを備えているが、iOSにもAndroidにも、WPFの基盤はもちろん存在しない。iOSやAndroidを対象とするライブラリは、WPFの機構を前提とするクラスを参照できない。UIが直接関係しない部分でも、WPFのDispatcherTimerクラスなどは存在しない。
PCLをXamarinで利用する
Xamarin.iOSおよびXamarin.Androidでは、PCLアセンブリを参照できるようになっている。より詳細に言えば、これらのプラットフォームを対象に含むPCLアセンブリは、同製品から参照できる。
Xamarin Studioには、「Portable Library」というプロジェクト・テンプレートが存在する。このテンプレートで作成されるライブラリのアセンブリは、PCLアセンブリとなり、Xamarin.iOSおよびXamarin.Androidで参照できる。デフォルトのプロファイルは「Profile 78」と呼ばれ、.NET Frameworkの他、iOS、Android、WP8、Windows Storeが有効になっている。Silverlightは対象外だ。
いったんプロジェクトを作成したら、そのプロパティ・ダイアログのビルド設定で、プロファイルを変更できる(次の画面)。
[Current Profile]を選択するコンボボックスと、その下に[Target Frameworks]を選択するチェックボックスがあるが、どちらを使用してもよい。このチェックボックスには、Xamarin製品を選択する項目は無い。これらは常に選択されており、オフにする余地はない(!)のである。
PCLプロジェクトのアセンブリ参照は、通常のアセンブリ参照とは少し異なる。参照アセンブリが直接表示される代わりに、「.NET Portable Subset」という参照アセンブリのグループのようなものが表示される。これを展開すると、現在のプロファイルに含まれるアセンブリを一覧でき、それぞれのアセンブリをダブルクリックすると、その内容を見ることができる(次の画面)。
PCLによらないコードの共有
PCLはAPIの制約が大きいので、コードの一部のみプラットフォーム固有の実装を作成したい場合もあるかもしれない。よく使われている方法としては、iOS、Android、その他のプラットフォームで、それぞれ別々のプロジェクトファイルを作成して、ソースファイルを「リンク」で含める、というものがある(次の画面はその例で、リンクファイルには赤枠のようなマークが表示されている)。これは、Xamarin製品がPCLをサポートする以前にMvvmCrossなどのライブラリでも用いられていた、典型的なトリックである。
この方式だと、作成されるライブラリは全て別々になるので、それらを参照するプロジェクトは、やはりプラットフォーム別に作成する必要がある。PCLは、プラットフォーム間でバイナリレベルのコード共有が行えるので、それを参照するライブラリもPCLにすることができる。
PCLを実現する仕組み
PCLの「使い方」(「作り方」)については、日本語でもすでにいろいろな解説があるので、これ以上ここで詳しく説明することはしない。その代わりに、ここでは、PCLが特にマイクロソフト以外のフレームワークについて、どのように実現されているのか、その仕組みを説明したい。
PCLは、従来の.NET Frameworkのプロファイルとは全く別のプロファイル(の集合)を規定しており、全く別のアセンブリ集合が、そのプロファイルに関連付けられている。PCLのフレームワーク・アセンブリには、通常のアセンブリとは異なり、実行できるコードが全く含まれていない。具体的には、これらのアセンブリはTypeForwardedToAttribute
という属性(System.Runtime.CompilerServices
名前空間)の集合体となっている。
この属性は、特にPCLを実現するために新しく作られたものではなく、以前から.NET Frameworkでも使用されていたものである。例えばWPFでXAMLサポートを実装していたクラスは、.NET 3.0のころはPresentationFoundation.dllに存在していたが、.NET 4.0以降はこれらの属性を伴ってSystem.Xaml.dllに含まれている。WCFのSystem.Runtime.Serialization.Json
名前空間に属する型は、.NET 3.5ではSystem.ServiceModel.Web.dllファイルに存在していたが、.NET 4.0ではSystem.Runtime.Serialization.dllファイルに移動している。
話をPCLに戻そう。このTypeForwardedToAttribute
の集合体となっているアセンブリを、「ファサード(Façade)アセンブリ」と呼ぶ(「façade」はフランス語だが、英語にすれば「frontend」あるいは「face」のような意味であり、デザインパターンの用語でもある)。PCLをターゲットにしたプロジェクトが参照する標準ライブラリは、このファサードアセンブリであり、PCLプロジェクトをビルドして生成されるアセンブリに含まれる型参照は、このファサードアセンブリに対する完全修飾名(fully qualified name)となる。
C#コンパイラーは、TypeForwardedToAttributeが適用されている型については、型の参照解決時に、その属性が指示するアセンブリを代わりに参照して型を解決する。PCLのファサードに含まれる型には、必ず対象プロファイル上に対応するアセンブリと型があり、アプリケーションの実行時には、その対象プロファイルのアセンブリのコードが実行される。
TypeForwardedToAttribute自体は、明示的にアセンブリを指定することはない。代わりにSystem.Type
オブジェクトを引数に取る。このオブジェクトが表す「型」はコンパイル時に解決されなければならず(そうしないとコンパイラーが型解決エラーを起こす)、またアセンブリ生成時に参照先アセンブリ(ポータブルでない)の完全修飾名が埋め込まれることになる。
TypeForwardedToAttributeが指定する参照先アセンブリは、実のところ対象プラットフォームによって異なる。マイクロソフトが公開したのは、.NET Framework 4.5に対応するPCLファサードであり、これらはmonoでも使用できるものの、.NET Framework 4.5と互換のプロファイルについてのみ意味があるものだ。「PCLをサポートする」プラットフォーム全てが、このファサードを用意することになる。Xamarin.iOSおよびXamarin.Androidには、Xamarinがビルドしたファサードアセンブリが含まれている。
なお、このPCLのプロファイルをターゲットにしたプロジェクトは、通常の.NETクラスライブラリとは別のプロジェクト種別となり(別のProject GUIDを持つ)、そのプロジェクトファイル(.csprojファイル)はMicrosoft.CSharp.targets
ファイルではなくMicrosoft.Portable.CSharp.targets
ファイルをインクルードする。このtargetsファイルは、アセンブリの参照解決先として、ファサードアセンブリのディレクトリを指定する。以上のような仕組みで、PCLはコマンドラインのMSBuildからもIDEからもビルドできるようになっている。
高度なPCLの作成とNuGetを使用した配布のトリック
PCLは、その性質上、対象フレームワークが提供するAPIの最大公約数的なものになるのが一般的だ。しかし、プラットフォーム別のアセンブリを用意することで、この限界を突破しつつ、ライブラリをPCLとして提供することもできないわけではない。ここでは、Xamarinのエヴァンジェリストが作成した、UI上のメッセージポップアップを実現するPCLのNuGetパッケージのサンプルを基に、その方法を簡単に紹介する。
.NET Frameworkでは、コンパイル時と実行時の型参照の解決に、アセンブリ名を含む完全修飾名を使用する。コンパイル時と実行時でアセンブリが異なっても、この完全修飾名さえ一致する型であると判断されれば、問題なくロードできて実行できてしまう。
この性質を利用すると、次のようなトリックが成立する。APIだけはPCLとして共通化しておいて、コンパイル時にはそのAPIを定義したアセンブリが参照されるようにしておいて、アプリケーションのビルド時には、そのAPIのプラットフォーム固有の実装を含むアセンブリが参照されるようにすれば、本来同一のDLLであるPCLに比べて、より幅広い機能を対象とするライブラリを作成して公開できるのである。
そしてこれは、NuGetを活用することで、ユーザーが自然に使用できるような方法で、ライブラリを参照できるのである。具体的には、API定義として機能するPCLを作成する。実装はまともに動作しなくてよい(アプリケーションからは呼び出されない)。そして、それぞれのプラットフォームに対応する実装となるDLLを作成する(AndroidではToastが、iOSではUIAlertViewが、Windows PhoneではMessageBoxが用いられている)。それらのアセンブリのクラスは、API定義のPCLと同じ完全修飾名となるよう、ネームスペースとアセンブリ名を同一にする必要がある。
そして、それぞれのプラットフォーム用にビルドされたアセンブリを、対応するプラットフォーム用のディレクトリに配置したNuGetの.nupkg
ファイルが作成されるよう、.nuspec
ファイルを記述し、これに基づいて.nupkgファイルを作成する。
このPCLを参照するアプリケーションのビルド時には、それぞれのプラットフォームに対応したアセンブリが、このNuGetパッケージの中から適宜選択されることになる。このような手順によって、PCLが共通APIの最大公約数の範囲を超える機能を提供できるのである。
もちろん、API定義と実装が分かれているだけでもPCLとしては十分であって、NuGetにする必要はないが、通常の参照DLLでは、API解決とアプリケーションのビルドで、別々の実体(ファイル)を使用することはできないだろう。そうすると複雑な参照の仕方をユーザーに要求することになってしまう。NuGetパッケージにしておくことで、ユーザーにとって違和感のない参照が可能になるのである。
PCLの課題
PCLはまだ真の意味でポータブルであるとは言えない。Linuxデスクトップ向けに利用できるようにするためには、制限のあるライセンスではなく、Apache 2などのオープンソースライセンスに基づいて配布されることが必要だ。この意味では、マイクロソフトもXamarinも、現在のPCLの配布形態が十分に良いとは考えていない。
Linux上のMonoDevelopでも、PCLのプロジェクトを「作成」することは可能だ。しかしそのプロジェクトのビルドは成功しない。なぜなら、PCLのビルドに必要な、肝心のファサードアセンブリが存在しないからだ。
現状、PCLのファサードアセンブリをLinux上で利用するためには、Windows上でMicrosoft .NET Portable Library Reference Assemblies 4.6をインストールした後、それらのアセンブリを手動でLinux環境のMonoインストール先にコピーする必要がある。具体的には、PREFIX/lib/mono/xbuild-framework/.NETPortable
というディレクトリを作成し(「PREFIX」は実際のmonoインストール先に置き換える)、その中にWindowsの%ProgramFiles32%\Microsoft .NET Portable Library Reference Assemblies 4.6
にある.zipファイルの内容を展開する(UNIX環境では「.(ドット)」で始まるこのフォルダ名は隠しディレクトリ扱いになることに注意されたい)。
これで、Linux版MonoDevelopでも、PCLのテンプレートが有効に機能するはずだ。Microsoft.Portable.CSharp.targets
など、PCLプロジェクトのビルドに必要なファイルは、別途、Monoがインストールしているはずである。
もう1つの課題は、オープン性にあるといえる。「System」で始まるPCLのAPIのカタログは、マイクロソフトが定義しており、Xamarinのファサードアセンブリは、それに従っているだけだ。これらに含まれない型が必要になる場合は、別のPCLアセンブリを作成すればよく、一般開発者としては問題ないが、プラットフォームの提供者が関与する余地は今のところ無い。PCLに関しては、JavaにおけるJSRのような存在は無い。
例えば、PlayStation MobileやUnityのような独自フレームワークの提供者がPCLのファサードを作成できて公開できたとしても、それが直ちにXamarin StudioやVisual Studioのプロファイル選択ダイアログに反映されることはない。もしマイクロソフトが新しくPCLプロファイルを定義することになれば、IDEサポートにも反映されるのではないか、と筆者は想像する。この辺りの仕事は、2014年4月に新しく設立された.NET Foundationが担うことが期待されるのかもしれない(もちろん、特にそのような声が上がらなければ、特に需要はないということで、現状のままで進行するだろう)。
※以下では、本稿の前後を合わせて5回分(第10回~第14回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
10. Xamarin.AndroidにおけるJava相互運用の仕組みと、Javaバインディング・プロジェクト
Xamarin.AndroidでJavaとの相互運用を実現するアーキテクチャについて、さらにメモリ管理などの注意点を説明。さらにXamarin.Androidの制限事項についても解説する。
11. Xamarin Studio/MonoDevelopの基本機能と、C#コーディング補助機能
MonoDevelopとXamarin Studioはどう違うのか? MonoDevelopの基本的な機能を解説。C#コーディング補助機能についても紹介する。
12. MonoDevelopにおけるビルド/実行/デバッグと、iOS/Android向けのGUIデザイナー
MonoDevelopでアプリをビルド/実行/デバッグするための機能を解説。iOS/Android向けのGUIデザイナーや、MonoDevelopのカスタムアドインについても紹介する。