6/24ジェネリクス勉強会資料(またはmonoランタイムのrgctx/gsharedvtについて)

6/24に行われたジェネリクス勉強会で、珍しく(?)ランタイムよりのトピックでジェネリクスについて語るセッションを担当した。generics on Xamarin productsという題目である。

connpass.com

ジェネリクスに関連するトピックなら何でもありという勉強会で、結果的に非常に濃密な内容のセッションだらけで、とても良い内容だったと思う。当日の様子はtogetterにまとめられている。

togetter.com

わたしの発表したスライドについては、以下で公開しているのだけど、

speakerdeck.com

スライドだけではどうにも理解が難しい内容がいくつかあると思う。今回の発表内容について詳しくまとめていたので、事後になってしまってやや残念感があるのだけど、公開しておきたい。*1

C#ジェネリクス

「Xamarinの」ジェネリクス、というトピックは、何が特殊なのだろうか?

XamarinはMonoを基盤とする開発・実行環境であり、C#標準に忠実に従っている。

まず、ジェネリクスにはいくつかのバリエーションがあることを理解する必要がある。C#ジェネリクスについて、C++のテンプレートプログラミングと同じようなものだと見なしていたり、Javaジェネリクスと同じような挙動を期待していると、いろいろ食い違いが生じてしまう。

C#のソースはCIL(MSIL、DLLやEXE)という仮想マシンコードにコンパイルされ、CLIの実行環境によって実行される。このCILの中には型の定義や参照が入っているが、この中にはジェネリックな型情報が含まれている。

それがどうした、と思うかもしれないが、これはJava仮想マシンコードには存在しない特徴である。Javaコンパイラが生成するバイトコードジェネリックな型情報を「含まない」(ここではざっくりと「含まない」と書くが、実のところメタデータとしてはジェネリック型情報がある程度含まれている)。そのため、実行時にはジェネリック型は「存在しない」。

また、標準C++では、そもそもコンパイル時に型情報が全て解決されて、実行時には型情報を使ったメタプログラミングを行うことはできない(C++方面にはRTTI・実行時型情報*2というものがあるが、メタプログラミングを行えるとは言いがたい。同様のトピックはDelphiにもある)。

(SwiftやObjective-C、Rustを話に含めると、static dispatch、virtual dispatch、dynamic dispatchといった概念も取り上げた上でジェネリクスを論じないといけなくなるので、ここでは割愛したい。)

これらに対して、C#ジェネリクスは、実行時にも存在している。このため、たとえばC#ではIList\<int>とIList\<double>は実行時にも異なる型になるし、C#におけるFooFoo\<T\>は別の型になる((実際、System.ServiceModel名前空間にはChannelFactoryChannelFactory\<T\>がある))。ECMA C#ECMA CLIを前提としており、ECMA CLIではこのジェネリックな型を実行時に適切にインスタンス化して実行する必要がある。

このようなジェネリック型システムをreified genericsという*3

C#コンパイラと.NET実行環境の選択肢

プラットフォームとランタイムの組み合わせ

2017年現在、.NETプラットフォームは、大部分がMicrosoftによって提供されているものだが、実装は単一ではない。Windowsデスクトップやサーバー環境で動作しているのは多くが.NET Frameworkであろう。Microsoft系プラットフォームではWinRTやUWPが使われているかもしれない。Windows以外のデスクトップ環境では.NET Framework互換環境としてMonoが使われる。クロスプラットフォームGUIの無いPC用アプリケーションや一部のサーバーアプリでは.NET Coreが使われているかもしれない。モバイルではXamarinが使われている(ひとことでモバイルと書いたが、iOSAndroid、watchOSやtvOS、Android Wearなどは少しずつ異なる)。そしてゲームではUnityも大きい。

これらは全て異なる実行環境であり、その動作の仕組みはプラットフォームによって異なっている。とはいえ、大まかには、ランタイムが.NETに由来するものか、Monoに由来するものかで分けることができるだろう。

platform .NET Framework UWP .NET Core Mono Xamarin Unity
runtime CLR CLR (Core)CLR Mono Mono Mono
API availability full subset subset full subset subset
Windows PC o o o (o) N/A o
macOS N/A N/A o o o (Xamarin.Mac) o
Linux N/A N/A o o N/A o
iOS (,watchOS,tvOS) N/A N/A N/A N/A o o
Android (, Wear, TV) N/A N/A N/A N/A o o
Windows mobile N/A o N/A N/A N/A o
Tizen N/A N/A o N/A N/A o

あるいは、もっと簡潔にこうまとめても良いかもしれない

Framework Runtime Platform
.NET Framework (CLR) Windows only
.NET for WinRT (CLR) Windows 8.0+, WinRT
UWP (CLR*) Windows 10+
.NET Core (CoreCLR) Windows, MacOS, Linux, Tizen
Mono (Mono) MacOS, Linux, Windows
Xamarin (Mono) iOS(,watchOS,tvOS), Android (,Wear,TV)
Unity (Mono) almost anywhere

いずれも細かい表ではあるが、それでもこのまとめ方はある程度「雑」で「恣意的」であることに気をつけておいてもらいたい。たとば.NET Frameworkに含まれるWindowsプラットフォーム固有のAPIに依存するもの(WPFやWFやSystem.Managementなど)はMonoでは実装されないが、それはここでfullと記すことに何ら問題はないという前提である。

今回のトピックはXamarinプラットフォームであり、XamarinのランタイムはMonoなので、以降、実装に踏み込むときは、Monoを探索していくことになる。

ちなみに、Xamarinと銘打たれた製品であっても、Xamarin.Formsのようなクロスプラットフォームライブラリの実行環境は、それぞれのプラットフォームによるものであることを意識しておいたほうがよい。たとえば、Windows MobileやTizenで動作するXamarin.Formsは、実際にはUWPや.NET Coreの上で動作している。これらはXamarinのUIフレームワークではあっても、Xamarinプラットフォームで動作しているわけではなく、今回のトピックからは外れることになる。

C#コンパイラ: cscとmcs

.NETプラットフォームにおけるC#コンパイラは、ランタイムほど多様ではない。簡潔な表になるがまとめておこう。

.NET <= 4.5 .NET > 4.5 .NET Core Mono < 5.0, XS Mono >= 5.0,VSMac Unity
classic csc Roslyn csc Roslyn csc mcs Roslyn csc mcs

この先を読み進める前に、ひとつ注意しておきたいことがある。この説明は、あくまでSDKに含まれるコンパイラの話であり、IDEを経由したビルドではIDEの規定するコンパイラがということだ。特にXamarin向けのコードをビルドしている場合でも、Windows上でVisual Studioを使用している場合は、コンパイラcsc(classicあるいはRoslyn)であり、そのことはビルドされたコードの実行に際して問題にならない。Xamarin製品でも使用されているMonoランタイムは、CIL準拠のコードを正しく実行できる。

Unityはやや特殊なので説明が必要だろう。Unityは2000年代からMonoをゲーム用に改造して極小化したプラットフォームであり、開発にはMonoDevelopを用いていた。そのランタイムは当時MonoプロジェクトをリードしていたNovell社から商用ライセンスを受けていた(そうしないとLGPLになり、商用ゲームの配布に困難を来すものであった)。しかしNovell社がAttachmate社となってMonoチームを切り離した後、XamarinはAttachmateとライセンス提携によって自由にmonoランタイムを利用できるようになったが、Unity TechnologiesではXamarinが改造を加えた新しいランタイムを使用できなくなった。もともとUnityはMonoランタイムに大きく修正を加えており、追従は簡単ではなかったこともあってか、Xamarinと改めてライセンスを契約することはなく、Unityは古いMonoを使い続けることになった。

この状態が、MicrosoftがXamarinを買収してMonoランタイムをMITライセンスに変更するまで続き、それまでは、C++とAOT(事前コンパイル)を前提としたIL2CPPのような仕組みによって、C# 6.0までのサポートが提供されていた(monoのコンパイラmcsはMITライセンスであったため、MonoDevelop上から最新のものを利用することができた)。Unity 5.4で最新のmonoランタイムに合わせたことになる。

もっとも、Xamarinの最新版はUnityの最新版よりさらに先を行っている。Mono 5.0から、C#コンパイラはRoslynに完全に置き換えられた。このMono 5.0は、Visual Studio for Macの正式版リリースとタイミングが合うように公開されたもので、つまりこの時期にリリースされたXamarinの各製品 (iOS/Android/Formsなど)は、Roslynを前提としたビルドシステムでビルドして実行できるようになっている。

Roslyn(csc)とmcsは何が違うかというと、まず(1)C# 7.0以降をサポートしているのはRoslynのみである。tupleやtype switchをmcsで使用することはできない。また(2)cscが生成するデバッグシンボルはpdbまたはportable pdb(拡張子はいずれも.pdb)であるのに対して、mcsのデバッグシンボルはmdbであり、これらは異なるフォーマットである(これはECMA CLIで標準化されていなかったのである)。Windows以外の環境でcsc/debug を指定して実行するとエラーになる。pdbWindowsプラットフォームを前提としたフォーマットだからだ。他にもいくつか違いがあるが、基本的にはmcsにはもう機能追加やバグフィックスがなされない、ということだ。

Unityはまだmono 5.0を前提とした製品になっていないが、いずれ追従するだろう(ただ、C# 7.0の機能に必要なライブラリがサポートされるかどうかはわからない)。

本節のまとめ

説明がだいぶ長くなってしまったので、この節の要点を2つだけまとめておこう。

Monoランタイムにおけるジェネリクスの実装

前節では、C#コンパイラの違いはXamarinプラットフォームでアプリケーションを実行する際に有意な違いはほぼ無い、ということを説明した。一方で、Monoランタイムによるアプリケーションの実行方式は、有意な影響をもたらす。その理由は後で詳しく説明するが、まずはその前提として、Monoが特にジェネリクスに関連してどのようにコードを実行するのかを説明しておこう。

EEと実行方式

ECMA CLIでは、CILプログラムのコード実行エンジン(Execution Engine、EEとも呼ばれる)の方式を規定していない。仮想マシンコードを実行する方式には、主に、仮想命令コードを逐次読み取ってランタイム自身が解釈して実行するインタープリター方式と、仮想命令コードを実際のCPUの命令列に翻訳してそれをCPUに直接実行させるコードジェネレーター方式があり、後者にはさらに実行時にこれを行うJIT(ジャストインタイム)コンパイル方式と実行前に行うAOT(事前)コンパイル方式があるが、どれもECMA標準上は問題ない。

Monoランタイムは、開発当初2年ほどはmintと呼ばれるインタープリターでCILコードを実行する方式であった。これは実装が最も簡単であったためである。コードジェネレーターは、CPUアーキテクチャごとに実装を書き分ける必要があり、これには少なからぬ労力が必要になった。しかしいずれにしろ、ほどなくMonoの主な実行エンジンはminiと呼ばれるJITエンジンとなった(現在でもJITが主流であると言ってよい)。

MonoでAOTが求められるようになったのは、RAM容量の小さい組み込み環境でMonoを使いたいというニーズが上がってきた頃からである。コードを事前コンパイルしておけば、それをROMに焼いてしまうことでMonoを利用できるようになる、というわけだ。AOTは、この時点では後で説明するような理由により「不完全」なものであったが、不完全であることはほぼ問題ではなかった。

どの方式にも長所と短所がある。インタープリタはランタイムで簡単に実装でき、クロスプラットフォームで実行できるが、実行速度では他の方式に劣る。それ以外の方式ではCPUアーキテクチャごとにコード生成実装が必要になる。JIT方式であれば事前にコードをプラットフォーム別にコンパイルしておく必要は無いが、変換処理は高速であることが求められるし、また実行時には生成されたコードのためにより多くのRAMメモリを必要とする。AOT方式であれば変換処理に時間がかかっても大きな問題はなく、コードはROMメモリに焼いておくことができるが、CPUアーキテクチャごとに生成しておく必要があり、また実行時にならないと必要がコードがわからない状況に対応できない(この問題は後で詳しく説明する)。

EE 実装の移植性 パフォーマンス RAM消費量
interpreter easy bad
JIT hard good
AOT hardest good

AOTとJITのどちらが高効率のコードを生成できるかというと、AOTのほうが良い傾向があるが、実のところコードに依存するので(最適化手法にもよる)、事前にコンパイル処理が完了しているAOTのほうが早いとは一概には言えない。

オブジェクトレイアウト

ランタイムが解決しなければならない課題のひとつは、CILメタデータに基づき、オブジェクト構造をメモリ上に展開して、実行すべきコードの所在を適切に解決することである。クラスにはメソッドがあり、それらは継承関係によってはvirtualであり、あるいはoverrideであり、その状態次第で実行されるメソッドが変わる。それらを、エントリポイントから順次解決していく必要がある(そして、呼び出されるメソッドはEEによって実行される)。

オブジェクト構造は、通常は、型情報が必要になった時点で、ランタイムがメモリ上に生成する*4。オブジェクトのレイアウトはクラスあるいはインターフェースの定義から固定できる(インターフェースの場合は、実際のクラスからそのインターフェースへのメンバーのマッピング処理が挟まることになる)。いったんメモリ上に生成した型情報は、その型が利用される限りは何度でも再利用でき、再度オブジェクトレイアウトの生成処理を行う必要はない。

というのが大前提である。

さて、ここにreified genericsが絡んでくるとややこしくなる。ある構造体Foo\<T\>にint型のメンバーA、T型のメンバーB、object型のメンバーCがいるとしよう(StructLayoutはSequentialであるとする)。

[StructLayout (LayoutKind.Sequential)]
struct Foo<T>
{
    public int A;
    public T B;
    public object C;
}

このオブジェクトのレイアウトに必要なメモリサイズは、Tの型が決まらないと分からない。Cのように参照型のメンバーについては、実際のオブジェクトへのポインタを格納するだけで足りるが、Tは構造体かもしれないし、このFoo型をどの型でインスタンス化するか*5によって、必要なサイズが変わる。

もうひとつ、今度は図解も交えて例を出そう。次のようなジェネリッククラスがあるとする。

class G<T> {
    void M (int X, T Y, double Z {...}
}

このクラスのメソッドMの呼び出しから生成されるネイティブコードのメモリレイアウトは、Tの型次第で変わる。

ジェネリック型引数によって変わるメソッド引数のメモリレイアウト

Tが参照型である場合は、ポインタサイズで固定できているが(上記の例ではMarshal.SizeOf(IntPtr) = 4の環境を前提としている。64ビット環境ならSizeは8になろう)、それ以外の型引数については、Tの型次第でメモリレイアウトが変わっていることが見て取れるだろう。

こうなると、オブジェクト構造は「インスタンス化されたジェネリック型」に特化して生成すれば良いのではないか、とも思えるが、そうなるとList\<int\>List\<byte\>List\<double\>…と、インスタンス化が必要な型は組合せ爆発的に必要になり、その割にはほとんどの内容は同一なので、メモリ効率が非常に悪い。

そういうわけで、monoランタイムでは、ジェネリック型については、型パラメータに依存する部分については特別に解決することにして、それ以外の部分は一本化してコード生成することにしている。これをgeneric code sharing(あるいはgeneric sharing、shared genericsなど)という。

これはerased genericsでは起こらない問題とも言える。erased genericsのランタイムにはジェネリック型情報が無いので、ListもListも違いはない。もっともプリミティブ型のintやdoubleをJava.Lang.IntegerやJava.Lang.Doubleに変換する過程でboxingは発生しているはずである。reified genericsのあるCLIでこれを愚直にやろうとすると、プリミティブな値型を参照型に変換するboxingが発生することになって、不要なヒープメモリ確保が生じてしまって、パフォーマンスが悪化するだろう。値型はboxingなしに利用できる必要がある。

monoにおける実装の詳細についてはGeneric Sharingのドキュメンテーション ページを参照。このドキュメント自体はランタイム実装者のためのもので、RGCTX(runtime generic context)といった単語が説明なく用いられているので読みにくいが、特にこのページからリンクされているランタイム開発者のblog postsはgeneric sharingの何が難しいかをいろいろと解説している。

ちなみにCLRの実装もmonoと同様のジェネリック共有が実装されているはずである*6。特にNGenについては、事前に既知であるジェネリックインスタンス型についてはコード生成していると説明されている。

この辺りの問題はLLVMベースのコンパイラでも、不要に膨大なコードを生成しないために必要となるはずだが、その辺りは今回調べるための時間が無かったので、詳しい人が解説してくれることを期待したい。また、この種の問題はJava方面で進行中のVarhalla Projectについても言えるはずなので(この辺りに詳しく載っているかもしれない)、この方面でも知見が広まると面白いと思う。

iOSジェネリクス制約

iOSの制約とAOTがもたらす問題

CLIのEEについて解説したとき、JITとAOTについては、大まかな違いしか解説しなかったが、事前にコードを生成しなければならない、というのは、実行時にならないと正確な型情報が分からずオブジェクトのレイアウトもできないジェネリクスにとっては、大きな問題である。

実のところAOTの実装は「不完全な」AOTであった、と書いていた理由はこの問題のためである。それでも、ジェネリックコード共有があれば、組み込み環境でも実行時に必要最小限のコードを生成すればよく(そのためにJITエンジンを搭載する必要は生じるが)、コードの実行が根本的に不可能になる、という性質の問題ではなかった。

しかし、時代は代わり、アプリケーションによる実行時コード生成が全く行えない環境が登場する。iOSである*7iPhoneのアプリケーションはAppleによる審査制であり、不正なコードを事後的に実行されないように、動的コード生成が禁止(明示的なAppleの許可設定が無い限り実行不可能)になっている。

Xamarin.iOSの前身はMonoTouchと呼ばれる製品で、2009年にリリースされたが、monoのAOTはこの頃にUnityでの成果から学びつつ実装されていた。iPhoneアプリにおける動的コード生成は当初から禁止されていたので、MonoTouchのリリース時には既にジェネリクスの利用には制約がある旨の注意書きが出されていた。

ちなみに、Xamarinのモバイル製品のAPIは、当初はSilverlightのCoreCLR(現在の.NET CoreにおけるCoreCLRとは、概念的には類似するが別物である)に関連付けられた、最小限のクラスライブラリに、非ジェネリックコレクションやXML DOMやXmlSerializerなど、もう少し古いAPIも付け加えたものだったが、もしSilverlightAPIしか無かったら、根本的にコレクションAPIの利用に困難を来していただろう。

Generic Value Type Sharing

このままでは実際のC#アプリケーションを作成する際に大きな制約になることは明らかだったので、Xamarinではこの問題を解決する仕組みを独自に開発した。それが、この節で説明するgeneric value sharingである。

問題の根本は、実行時にならないと解決しないようなジェネリック型の利用場面で、必要なスタックメモリのサイズが確定できないことにある。この問題を解決する方法は2つ考えられる。

(1)gsharedvtについては「十分に大きい」サイズの値型用メモリを確保しておくことによって、ある程度の大きさまでの値型なら、実際の型を問わず格納・実行できるようにすればよい。筆者の理解では、Xamarin.iOS 6.4時点で導入されたgsharedvtの実装はこのアプローチだ。このメモリレイアウトを先程までの図に倣って示す。Xは固定サイズである。

gsharedvtについて十分に大きなサイズXを確保する解決策

(2) gsharedvt な引数については、実行時ジェネリック情報をもとに、実際の値型に必要なメモリスペースをスタック領域に確保し、その後CPUアーキテクチャ別のトランポリンを使って、実際に必要なメモリレイアウトを構築してから呼び出す。こちらが現在のmonoランタイムで採用されているアプローチである。ヒープ領域にメモリ確保しないので、GC呼び出しを発生させることはない(これはILのlocalloc相当の呼び出しで実現しているようだ)。monoランタイムは、メソッド呼び出しにgsharedvtである引数が含まれていることを検出すると、トランポリンコードを使って、スタック領域に呼び出し先のメソッドが期待している引数のメモリレイアウトを形成し、メソッドを呼び出し、その後に以前の状態にメモリを復元する作業を行う。

このgeneric value sharingは、2016年にXamarinプラットフォームがオープンソースで公開される前は、Xamarinのプロプラエタリなmono拡張として非公開状態であったが、2016年にOSS化されてmonoに取り込まれている。gsharedvtというキーワードでソースコードを探索すると、mono/miniディレクトリ以下にmini-(arch)-gsharedvt.ctramp-(arch)-gsharedvt.cというソースが見つかるはずである。また、OSS化に伴って、ドキュメントも追加されている。

ちなみに、この機能はXamarin.iOS 6.4で導入された当初はiOS固有のオプションで、mtouchコマンドの引数に-gsharedvtを追加しないと有効にならなかったが、現在ではこのオプションには意味はなく、必ず有効になっている。プラットフォームもiOS限定ではない。ただし、もしかしたらgsharedvtが有効なCPUアーキテクチャは限られているかもしれない((monoのソースコード中にmono_arch_gsharedvt_sig_supportedというMONO_INTERNALでマークされた関数があるが、これを呼び出している部分の存在は確認できなかったので、もう全てのアーキテクチャで有効になっている可能性が高い。))。

Xamarin APIにおけるジェネリクス

ここからは、少しだけ実際のプラットフォーム ネイティブAPIとMonoランタイムのinteroperabilityについて言及しておこうと思う。

Objective-CC#Interop

Xamarin.iOSおよびXamarin.Mac - 毎回これらを併記するのは面倒なので、以降はOSSリポジトリ名に合わせてxamarin-maciosと書くことにする - のプラットフォーム ネイティブAPIObjective-Cが基本となっている。xamarin-maciosの基本的な仕組みは、monoのembedded APIとObjCRuntimeを経由した、CLIObjective-Cの相互運用である。Objective-Cはdynamic dispatchなど実行時にバインドすることを前提としている。Objective-CAPIと相互運用する.NETのオブジェクトは、Foundation.NSObjectクラスを継承していなければならない。

Objective-Cには、本当のジェネリクス…もといreified genericsは存在しないので、C#ジェネリック型引数をObjective-CバインディングAPIで定義するためには、そのジェネリック型引数をNSObjectから継承させなければならない。バインディングAPIはRegisterAtributeを使って明示的に定義するか、既存のバインディングをオーバーライドするものであり、バインディングでないAPIでは意識しなくても良い。また、API定義のみの問題であり、メソッド本体の中などでは、NSObjectから継承していないものでも利用できる。その型参照はmonoランタイム上で解決されるためである。

また、インスタンス化されたジェネリック型(instantiated generic type)をバインディングAPI定義に含めることはできない。ObjCRuntimeにはreified genericsが無いため、インスタンス化されたジェネリック型を解決できないためである。これは型名解決の問題なので、その型から派生する型を定義すれば良い。

public class GenericUIView : Generic\<UIView\> { ... }

xamarin-maciosとジェネリクスに関する制約については、その問題に特化した公式ドキュメントのページがあるので、詳しくはこれを参照されたい。

Xamarin.Androidにおけるジェネリクス

Xamarin.AndroidのプラットフォームAPIJavaであり、Xamarin.AndroidはJNIとmonoのembedded APIを経由して、CLIJava APIを相互運用している。Javaもerased genericsの世界であり、バインディングAPIにはJava.Lang.Object型のインスタンスを渡す必要がある。もっとも、Javaはインターフェースプログラミングの世界であり、Javaのインターフェースに対応するXamarin.AndroidのインターフェースをJava.Lang.Object(java.lang.ObjectにバインドするXamarin.Androidのクラス)から派生させることはできないので、バインディングCLI側インターフェースではIJavaObjectというXamarin.Androidのインターフェースを継承させる。Java.Lang.ObjectクラスはIJavaObjectを実装しており、Android APIに対応するXamarin.Android APIのクラスは、全てJava.Lang.Objectから派生している。

Android APIに対応する型は、基本的にジェネリック型ではない(ごくまれにジェネリック型のバインディングが存在するが、これは便宜上作られた手書きのクラスである)。これは、JNI経由でCLIオブジェクトを生成する場合にジェネリック型をインスタンス化する方法が分からない、という、xamarin-maciosと同様の制約である。

xamarin-maciosと同様、バインディングAPIはRegisterAttributeやオーバーライドによって識別されるものであり、RegisterAttributeはジェネリック型では使用できず、ジェネリックメソッドは定義できない…といった制限も、xamarin-maciosと概ね同様である。詳しくはXamarin.Androidのドキュメンテーションを参照されたい。

Future topics

デフォルトインターフェースメソッドとジェネリクス

今後C#に追加される予定の言語機能として、デフォルトインターフェースメソッドがある。インターフェースのメンバーを宣言して、そのデフォルト実装も追加できる、そして派生インターフェースでオーバーライドできる、というものだ。Javaのデフォルトインターフェースメソッドとほぼ同様の機能である。派生インターフェースでオーバーライドしたいという要件さえ無ければ、C#では拡張メソッドによって対処できていた問題だ。

Xamarin製品では、このC#のデフォルトインターフェースメソッドを活用できる場面がある:

  • xamarin-maciosにおけるプロトコルのメンバー: Objective-CとSwiftのプロトコルには、実装が必須でないものがある。このようなメンバーについて、Xamarinのバインディングにおいて実装を強制することはできないので、これらはインターフェースメソッドとすることは出来ず、ExportAttributeなどを使用してサポートすることになる。これが空実装のデフォルトインターフェースメソッドが使えるようになれば、普通のインターフェースに転換できる。
  • Xamarin.AndrodのJava APIにおけるデフォルトインターフェースメソッドのバインディング: Java8で追加されたデフォルトインターフェースメソッドが、Android APIでも使われるようになってきているが、現状これらはC#で対応する機能が存在しないためバインドされていない。デフォルトインターフェースメソッドがC#側でも実現すれば、これは単純にマッピングできるようになると期待できる。

デフォルトインターフェースメソッドはまだ仕様策定中の機能であり、さまざまな調整が必要となるはずである。特にECMA 335とは矛盾する仕様があるため、仕様の改定も必要になる。

ちなみに、プロトタイピングの過程でshared genericsに関連して、coreclrチームからmonoランタイムチームに提示された面白い話題があったので、余談として紹介しておきたい。

以下はデフォルトメソッド実装をもつインターフェースである。

interface IX<T>  {
    int Get();
    void Set(int val);
    int M()
    {
        int val = Get();
        val++;
        Set(val);
        return val;  
    }
}

以下はこのインターフェースを複数のジェネリック型パラメータ(IList\<Z>とIEnumerable\<T>)で実装した構造体である。

struct P<Z> :
 IX<IList<Z>>,IX<IEnumerable<Z>> {
    int v;
    public int Get()
         { return v; }
    public void Set(int val)
         { v = val; }
    int IX<IList<Z>>.M()
         { return ++v; }
}

そして以下のクラスに含まれるメソッドの中では、このデフォルトインターフェースメソッドを呼び出している。

class TestClass {
    public static int Zap<T,U>(T, t)
        where T:IX<U>
     {
         return t.M() + t.M();
     }
}

さて、以下のメソッド呼び出しは、それぞれ何を返すだろうか?

Zap<P<object>,IEnumerable<object>> (new P<object> ()); // (1)
Zap<P<object>,IList<object>> (default(P<object> ()));  // (2)

実のところ、クイズが目的ではない。それぞれの戻り値は2と3になるべきである。彼らの問題は、上記のZapメソッドをILに変換しそれをgeneric sharingで変換したメソッド呼び出しの実体にある。

IL_0003:  constrained. !!T
IL_0009:  callvirt   instance int32 class IX`1<!!U>::M()

デフォルトインターフェースメソッドの変換結果は、Pのデフォルトでないインスタンスについては、これをbox化したオブジェクトに対して呼び出すことになり、Pのデフォルト値については、box化せずに呼び出すことになるが、このgeneric sharingによるコードの呼び出し結果について、unboxする必要が「あるかもしれないし、ないかもしれない」という呼び出しを行うことはCLRの実装としてはできない。どうすべきか?

…実装上解決すべき問題は、このようなものがある。非常にややこしい例だ。

coreclrのプロトタイプ実装からは、他にもJITにウソ情報を流さないと正しく処理できない状況などもあることが見て取れる。これは多分最終的な実装でもこのままで問題ないのだろうが、実装を複雑にする要因になっていることはわかるだろう。

SwiftNetifier

詳しいことは全く報じられていないが、XamarinはBuild 2017でSwiftNetifierというプロジェクトの存在を明らかにしている。Swift APIをもとにxamarin-maciosのバインディングを生成するツールである。Swiftのジェネリクスはreified genericsであり、reified generics同士がうまく相互運用できるのかどうかは分からない。とはいえ、実行時にObjCRuntimeでインスタンス化できるのはNSObjectのみであろうから、現在と同様の制約はなおも存在するのではないか、というのが筆者の考察である。

Project Varhalla

Androidプラットフォームで実現するのはいつになるかわからないが、Javaの将来版ではジェネリクスC#と同じreified genericsになることが予定されており、これを実現しようとしているのがProject Varhallaである。Varhallaに相当するものがAndroidにも登場するとしたら(そしてその時点でもAndroidJavaに基づいているとしたら)、いずれはXamarin.Androidでもreified generics同士の相互運用を実現することが必要になってくるだろう。

その未来のほうがすっきりした相互運用を実現出来る可能性も、無いわけではないが、基本的には過去のライブラリの後方互換性を意識した製品をリリースすることも期待されているので、現時点でそこまで考えることは無いかもしれない。

Swiftにせよ、Varhallaにせよ、reified genericsの相互運用に固有の問題があるとしたら、今後明らかになっていくであろうトピックであり、ジェネリクスは古くて新しいトピックであり続けるのではないか、と暗雲を投げかけて(?)筆を置くこととしたい。

*1:本当は勉強会の予習資料として公開するつもりだったのだけど、実装の調査が不十分なままで出来ていなかった。

*2:https://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI

*3:reifyは辞書的には「具体化する」という意味らしいが、それが直感的かどうか筆者には判断しがたいので、以降も言及するときはreified genericsと書くことにする

*4:その前段階として、マネージドコードとしての型参照やアセンブリの解決をAppDomainなどをもとに解決するステップもあるのだけど、これは今回のトピックとほぼ関係ないので割愛する

*5:オブジェクトのインスタンス化ではなくジェネリック型のインスタンス化であることに注意

*6:「Profesional .NET 2.0 Generics」のセクション “Under the Hood” を参照

*7:登場時はiPhone OSであったが、説明の簡略化のため以降も単にiOSと記述する

Essential Xamarin: postmortem

今年は珍しくGoogle I/Oに来ています。いろいろ聞く準備が出来ていなくてあかん…

さて、技術書典2/超技術書典で販売していたEssential Xamarinについて、いくつかお知らせがあるので、ついでに(?)技術書典へのサークル参加の事後的な情報も含めて、書いておこうと思います。

boothでEssential Xamarinの販売を開始しました

先月下旬にbooth.pmに入庫して以来、首を長くして待っていたのですが、ようやく販売できる状態になったので、ショップページを公開しました。

xamaritans.booth.pm

今回、同人誌の印刷から販売まで、初めてのことが多く続いているので、いろいろとなるほどなあという気持ちでやってきましたが、ひとまずご要望を何件かいただいていた、「遠隔地の方でもちゃんと入手できる」状態になったので、ひとまず技術書典2にかかるタスクは(外向けには)片付いたと思って安心しています。

技術書典2での参加実績と制作情報

技術書典2では、Yin/Yangともに100冊ずつ持って行ったのですが、おかげさまで、11時の一般入場開始後、2時間半で完売しました。ご購入いただいた皆様ありがとうございました。技術書典2そのものも、大雨の中、3000人以上の方に参加していただいて、運営スタッフとしても嬉しい気持ちです。

初参加でこれだけ売れたというのは、運良くMono/Xamarin開発の最先端執筆者をかき集められたということもありますが、技術書典というイベントの参加者層の高いアンテナと購買行動の成せる業というものでしょう。次回も何とか参加できるかたちにもっていけたらな…と思っています。Mono/Xamarin関係で同人誌に参加してみたい方がいたら、われわれにアピールしていただければと思います。

販売部数が想定できない…と言われていた技術書典ですが、最初は各50部くらい刷っていこうかな、くらいに考えていました。すると、公開してから「余ったらJXUGで販売すれば良いのでは」とか「技術書典には行けないから通販してもらえないか」といった話が出てきまして、確かにそれならもっと刷っておいたほうがいいかな、と思って100部ずつにしたのです。この時は、これでもまだ冒険しているつもりだった…!

執筆作業は、ほぼ募集要項で書いた通りに進めました。1月に募集開始して2月初頭くらいからゆるゆると書き始め、3/4に第一締切(Nintendo Switchの発売日に合わせた感じです)、その日から相互レビューを割り当てて進める、という流れで進めました。〆切後から参加したメンバーもいて、レビューされていない原稿もあったりするのですが(わたしのだ…!)、まあその辺は割り切りました。

あとは、個人的にですが、うちのサークル以外でも、技術書典の執筆期間中に(というものはないのですが)vscode-language-reviewの問題の報告がいくつか上がってきたりするので、それに対応するなどしていました。主にRe:VIEWを使った執筆中にしか使われないvscode拡張なので、自分の執筆作業中にも上がってくるという…。技術書典3が始まる前に手を入れる時間が作れたら良いなあと思っています。

技術書典2の後は、超技術書典向けの在庫と、今回の通販対応を考えなければならなくなりました。しかしどれくらいの部数が求められているのか、さっぱり読めなかったので、twitter上でざっくりとアンケートを取ってみました。

予想していたよりもはるかに大きい数字が出てきたのでびっくりしたわけですが、じゃあ少し多めに作って、余ったら他のイベントなどでも配布しようかと、だいぶ余裕のある数字で増刷しました。そしたら、超技術書典のほうでは、技術書典2の半分も売れていないので(そもそも技術同人誌を買いに来るイベントではない)、今は「がんばって売り切らないと余る」感じです(!)

Essential Xamarinの商業出版が決定しました

さてもうひとつ。技術書典2の後、インプレスR&Dの方からコンタクトがあり、このクオリティなら十分だから商業出版で出してみないかというご相談をいただきました。告知で「商業レベルで出してもいいんじゃないのこれ…?」って書いたのですが、完全に計画通り…!

インプレスにはNextPublishingというオンデマンド印刷中心のブランドがあるのですが、Essential Xamarinもここから出すという流れで動いています。また、一般の書店で紙の形式で並ぶということは、ほぼ無さそうです。

商業版は、同人版に執筆陣がいくらか手を加えたものを1冊にまとめて、3000円以下くらいの価格で販売する予定です。夏頃に刊行できれば良いと思うのですが、現時点でわたしが手直しの進捗ゼロなので…(de:code直前ですし…)まあなんとかします(何)

ちなみに、このこともあって、同人版の価格は、本当は1500円にしようと思っていたのですが、それでは高すぎるので、紙の書籍版、ダウンロード版ともに1000円としました。ダウンロード版は商業版が販売開始したら終了する予定です。(まあ余ったらちょっと困るというかもったいないのは紙のほうなのですが。)

とりあえず、こちらは続報をお待ちください。

(お知らせ) Xamarinを支える技術 at de:code2017

こんにちは、「ゴールデンウィークがあと1ヶ月続けばいいのに」とついうっかり書いたばかりに、そのGW中にチーム全体がリストラされることになった6年前を思い出しているatsushienoです。ホント、GWがあと1年くらい続けばいいのに

さて今日は軽くお知らせです。

日本マイクロソフトが5/23、5/24の2日間で開催するde:code2017で、50分のChalk Talkをやります。

Sessions - de:code (decode) 2017 | 日本マイクロソフトの開発者/アーキテクト/IT Pro 向けイベント - Microsoft Events &amp; Seminars

最初はセッションでという話で来たのですが、わたしが直前まで渡米していてちょっと難儀するかもしれないという話をしたら、うまいことチョークトークに転換して、時間も最後の最後のひとコマに調整していただけました。(感謝しか無い)

なにやらde:codeのトークセッションには全てレベルが割り当てられるらしく、500というのが指定されていて(100から500まであって500は2つしかないらしい)、これは要するに「好き放題しゃべってくれ」という趣旨のものだと思うのですが、わたしは日本法人の基準とか知ったことではないよくわからないので、いつものように適当にお話することになると思います。

お題は「Xamarinを支える技術2017」としました。これはもちろん某書のタイトルパクリなのですがXamarinの皮相的な使い方とかは他の人たちに任せて、こちらではXamarinはどういう技術を構築して今あるものを可能にしてきたのか、今後はどういう技術が求められていくのか、それにはどう対応していくのがいいか、といった話をしたいと思っています。

セッション時間は50分あるので、長くても半分くらいで収めたいのですが、折角なので(?)、今回はメイントピックに沿った範囲で少し低レベルなところをお話ししようと思います。MSILよりちょっと低いあたり。ただ、ガチガチのコードの話をするとしんどいと思うので(わたしも自分で実装しているわけではないのでややしんどい)、少しふわっとまとめていきます。

まあそれだけだとさすがにコアなXamarinユーザーの皆さんでもしんどいと思うので、Buildで何かしらアナウンスされるであろう、もうちょっと上の部分の動向も話に含めたいと思います。VSMacのプレビューなんかはもう誰でも試せますしね。

わたしの担当するチョークトークというのは、参加型のセッション、のようなものです。わたしが一方的にしゃべって皆さんがフンフンと頷いていたら終わる、というものではありません(当日の空気次第ではそうしますが)。わたしと雑談しにくるつもりでいらしてください。あれ、これっていつもコミュニティでわたしが雑談している状態と同じじゃね? Xamarinに純粋に技術的な興味のある皆さんと直接対話できる貴重な機会()なので、楽しみにしています。もちろんXamarinを使っていない方も大歓迎です。ぜひ参加してわれわれに外の世界の話を聞かせてください。

また、チョークトーク セッションの前に、こういう質問や話をしたい、ということがありましたら、事前にわたしにお伝えしていただければ、出来る範囲で回答を用意したいと思っています。このエントリのコメント欄やはてブmastodonアカウントでお伝えください。あるいはtwitter#decode2017xamarin などと付けてもらえれば、不定期的に探します。

さて、de:codeに参加される方にひとつお願いがあります。セッション評価は、皆さんの技術的良心に従ってつけてください。セッション講師が「いい評価を付けてください」とお願いしたから、とか、推しセッション講師だから、とか、講師に自己アピールして取り入りたいから、といった非技術的な理由で、評価が歪められ、真に価値のあるセッションやスピーカーがいなくなってしまったら、それは日本マイクロソフトにとって大きな損失なのです。

Satya Nadellaが率いるMicrosoftの社是は「Microsoft製品をもっと使ってもらうこと」ではありません。米Microsoftが目指しているのは「IT技術によってeveryoneをempowerしていくこと」であり、ユーザーにとって最適な技術を提供していくこと(もちろんMicrosoft自体がその場にあり続けられるよう努力していくこと)なのです。日本マイクロソフトの従業員が、自分たちだけを売り込むような振る舞いを見せていたとしたら、それは非常に非Microsoft的なことです。

de:codeに参加される皆さんも、セッション中にそういったノイズが多すぎるなと思ったら、コメントや評価で指摘してあげてください。

それでは、de:codeでお待ちしています。(もし参加できる余裕のある企業などの方がいたら是非参加してあげてください。日本MSが喜びます。) わたし自身は、自分でもかつてまとめたことがあるlanguage server protocolとTypeScriptのセッションや、わたしの裏番組でやっているWindowsアプリケーションの実践的なデバッグ・トラブルシューティング手法などが紹介されるセッションに興味があるわけですが、特に裏番組はさすがに無理だし惜しい…!という気持ちです。

Essential Xamarin -at- 技術書典2 -and- 超技術書典

昨年6月に開催された技術書典が、今年は数倍の規模になって帰ってきます。

techbookfest.org

昨年はTechBoosterのAZ異本 grimoire of AndroidにXamarin.Android SDKの解説を寄稿しただけ(!?)でしたが、今年はXamarin同人誌を発行してみようよ、とtwitterで呼びかけてみたところ、何人かの強者が名乗りを上げてくださったので、それなら一度ちゃんと出してみようと思って、サークルとして登録してまとめることにしました。*1

https://atsushieno.github.io/xamaritans/tbf2.html

最終的に6名の執筆メンバーから、かなり気合を入れた原稿が続々と上がってきまして(50ページ以上ある原稿が3本くらい上がってきて、読むだけでも割と大変でした)、250ページを超える大作になってしまいました。1冊で収めるといろいろ大変なことになるため*2、2冊に分冊して発行することになりました。

内容としては、世界レベルで見られることのないXamarin.Mac開発を懇切丁寧に解説した記事、Xamarin.Androidを話題の中心にしつつリアクティブプログラミングまで押さえる全方位的な記事、Prism.Formsの使い方について「全部出し切った」長大な記事、標準でサポートされていないSoCでembedded monoを動かすためにあらゆるカスタム最適化ビルドを試みる記事、BLEのXamarin Pluginを素材としてクロスプラットフォーム開発にXamarinを使う記事など、「商業レベルで出してもいいんじゃないのこれ…?」と思えるようなものが多数含まれています。

表紙も知人にお願いしてうちにある猿連中を渡して描いてもらったのですが、

f:id:atsushieno:20170330191800p:plain

かなり本格的な仕上がりです。あの連中は割と手足の融通がきかないので、イラストでないとこんな4人組は猿人形からは作れません。これで釣られて買いたいという方が多くて、してやったりと思っています。

ちなみに今回のタイトルは Essential .NET のオマージュです*3。Build insiderの連載はインサイド.NET Frameworkのオマージュだし、今度出てくる予定のXamarinを支える技術はもちろんいま流行りの「Androidを支える技術」のオマージュだし、わたしの出してくるのはそういうのだらけですね。

初めてのサークル参加というか初めての同人誌作成作業で、いろいろおっかなびっくりやっているのですが、とりあえず入稿まではやったので、何とか出せそうです。

そんなわけで、サークルとしては4/9の技術書典2(う-13)と4/30(あ-12)の超技術書典(2日目のみ)でお待ちしています。わたしは技術書典では主にイベント自体のスタッフとして活動することになるので、ほとんど少し離れた場所にいると思いますが、来場されたらぜひ探してやって下さい。

あと、地方の方などでイベントには参加できないのだけどほしいという話を何件かいただいているので、通販や電子版も用意するかもしれません。技術書典が終わって落ち着いてから対応したいと思います。

*1:Xamaritansというのはよく英語圏のXamarin界隈ではたまに使われるのですが、昨日も「サマリア人をもじっているなんて分かってもらえるのか」などと指摘されていたりして…まあ教養の問題ですね(!!)

*2:まあ主に印刷所に送るとき140ページを超えると締め切りが早まるみたいな、個別的な事情です

*3:Root of Xamarin Frameworkとかにしてもよかったんだぜ…?

Deemo 2.0の聖地を巡礼した話

Deemoという音ゲーがある。台湾のゲーム開発会社Rayarkの、代表作と言ってもいいと思う。主としてピアノ曲で構成された楽曲に合わせて「ピアノを弾く」類の、ルールの簡単なゲームで、音楽の美しさと、背景にある不思議な世界観のストーリーとグラフィックスが人気だと言えるだろう。楽曲は概ね台湾と日本のインディー系ミュージシャンから提供してもらっているようだ。

play.google.com

今日はそのネタバレ話を書こうと思う。ネタバレを見たくない人はここでこのページを閉じたほうがいい。読みたい人は「続きを読む」から先に行ってほしい。

続きを読む

Xamarin.Forms本 邦訳読書会について

以前JXUGのイベントのどれか(多すぎて忘れちゃった)でちょろっと告知したことがあったと思いますが、Charles PetzoldがXamarinにjoinして執筆している Creating Mobile Apps with Xamarin.Forms(以下「Forms本」) の日本語訳作業が進行しています。現在、藤原さん(@yfakariya)とわたしがほぼ半々ずつ監訳するという感じで進行しています(ここまでの実績に基づく予想値)。

ただ、原著が1200ページくらいある大作なので、ごくわずかな監訳リソースで見ていると、それなりに不安感があります。そこで、Forms本の進行中の訳本の原稿をもとに読書会を開いて、希望する人に読んでもらい、何かしらのコメントやフィードバックをもらえたらもらおう、というのを思いついたので、発行する日経BPさん、それと監訳チームのひとりである田淵さん(@ytabuchi)にご協力いただいて実現することになりました。

そういうわけで、これからしばらく、多分半年弱くらいの間、20章以上を十数回にわたって読み解く読書会をやろうと思っています。かなりの回数になるので、たぶん週1回くらいやることになると思います。紙に印刷した訳本の一部をお渡しして、それを読んでいただくことになる予定です。

そこで、まず参加者を募集したいのですが、いかんせん勉強会の内容の性質から、人数は多くても10人弱、そしてメンバーはなるべく固定しない(主にわたし、たまに田淵さんがホストするところだけ固定)、参加者はわたしが諸々考えて調整させていただく、という運用を想定しています。(実際に参加者が多く集まらなければ、実質的に固定メンバーになる可能性もあります。) ベテランの視点も重要ですし、初心者の観点でもコメントをもらえればいいと思っているので、Xamarin.Formsに技術的な関心がある方、という以上の条件は今のところ考えていません。

開催日時は、第1回のぶんから未定なのですが、今のところ「水曜・木曜の19:00くらいから」を想定しています*1。一応、毎回connpassあたりにイベントを立てようと思っています。

会場については、少し奇策を考えています。オフィスの会議室などをお貸ししていただけるところを転々としたいと思いつき、実際にいくつかの場所はお借り出来そうなので、試しにそれで回してみようと思っています。わたしが東京にいるので、だいたい東京開催になると思います(ごめんなさい)。書いていて思ったけど地方巡業するのもいいな…(!) あと、この勉強会を開催するために会場をお貸しいただける会社さんなどありましたら、ぜひご相談させてください。

というわけで、近々イベントを乱立(毎週分)させようと思っています。twitterで @atsushieno から告知しますので、そこでチェックしてくださいませ。

1/12追記: イベント登録を始めました。各回ここから辿れると思うので、チェックしてみて下さい。

xamarinformsbookreading.connpass.com

*1:ノー残業デーが多そうな水曜を主に想定しつつ、曜日を固定すると参加できない層のために木曜も考える、という感じです

楽器を作ろうハッカソン

年末からこんなに連続投稿していて、どうしちゃったんだろうわたしもうすぐ氏ぬのかなみたいな感じになってきましたが、皆さんいかがお過ごしでしょうか。

そんなわけで(?)、今日はイベントの直前告知(!)です。RasPiなどを使って楽器などをハードウェアハックしてみたい人のための集まりです。

insthackersnewbies.connpass.com

適当に思いついて開催するので、参加者が具体的に何をするかは参加者次第です。RasPiでもいいし、そいつにAndroid Thingsを載せてただのAndroid開発にしてもいいし、Arduinoでもいいし、素材は問いません。ハードウェア系のハックということになっていますが、実際に行う作業は純粋にソフトウェアだけかもしれません(わたしが今のところそうなりそう)。

成果発表は特に予定していません。5時間弱で成果が出るのを目標とするのもどうかと思いますし。成果・やりたいことを問わず、しゃべりたい人は適宜雑談してください(「みんなに聞いてもらう」時間は設けない予定です)。

なおtwitter上で会場未定で応募をかけたところ、株式会社ISAOさんのオフィスを会場として提供していただけることになりました(ありがとうございます)。電源とwifiは使えるようです。ハンダ付けなども注意して作業してもらえるならOKだそうです。(自分で持ってくる必要があります。はんだごての備品はありません。)

秋葉原付近なので、いざとなれば部品の買い出しに行くことも出来ます(たぶん往復+買い物で1時間くらい見たほうが良いでしょう)。

わたしも含め初心者が多いと思うので、まだよく分からない…という方でもお気軽にどうぞ。