Essential Xamarinが"技術書典シリーズ"のひとつとして刊行されます

少し前にお知らせしていたことですが、われわれのXamarin同人誌「Essential Xamarin」が、9/1にインプレスR&Dさんから出版されます。

prtimes.jp

商業化したということで、販路が広がって、Kindle版も出ています*1

https://www.amazon.co.jp/dp/B07539YT44/www.amazon.co.jp

1冊になったので、この4体も当初の計画のように1枚絵になりました。

商業化に際しては、ひらのさん(@ailen0ada)によるXamarin.iOSの記事が追加されたり、最新情報に合わせたアップデートが行われたりしています。あとプロの編集さんが手を加えた原稿をgit diffで眺めましたが(編集作業のやり取りもgithub上で行っていました = 編集済みの原稿をcommitしていただいていました)、やっぱりプロの作業は違う…われわれも相互レビューしているのですが、文言レベルでより誤解を招かないようなかたちにする修正などは、経験が出ますねー。

Essential Xamarinは、軽い気持ちで執筆者を募集したら、気づいたらXamarinコミュニティのガチ勢が本気で書いた原稿だらけになって、これは商業化できるレベル…!と思っていたら、実際に商業化の流れになって、いい話になったなという感じでした。

もうひとつ面白いのは、本書は新しく「技術書典シリーズ」というコレクションの2冊めの本として出るということです*2。技術書典はもともと同人誌から技術書界隈を元気にしていこうというノリで始まったものでしたが、同人誌から目立ったものを出版社が拾い上げて商業化する流れがあって、既に何冊か出ています。

技術書典は、そういう意味では「宝の山」なわけです。当然、出展者の数も多いわけです。前回の技術書典2が190件近くあって、10月の技術書典3も会場が同じだしほぼ同数でしょう。そこから拾い上げられた書籍、というある種のクロスボーダーなシリーズということになるわけで、これが通好みのものなのか、完成度が高いものなのか(あるいは全然別の評価が妥当なのか)はわかりませんが、ひとつのブランドとして通っていくと面白いなと思います。

ちなみに、技術書典3には、この同人誌版のEssential Xamarinも売り物として持っていく予定です。同じ会場でインプレスR&Dさんがこの商業版も販売する予定なので(その案内もこちらのブースで出す予定です)、どのように売れるかは見てみたいですねー、という話を出版社の方としていました。

技術書典は、わたし自身は運営にも片足を突っ込みながらサークルも回しているという特殊事例なのですが(それで特別扱いされることは無いです)、サークルの回し方なども初心者なりに適宜広めながら技術書典3に向けて進めていこうと思います。Xamaritans(サークル)のほうは、初稿締切までは3-4週間ですが、まだ執筆者も募集しているので、参加してみたいという方がいたらお気軽にご連絡ください。名前はXamarin系ですが、Xamarinと関係ない記事も書かれる予定ですし、商業化クオリティ…!とか気負ったことは考えずに、同人誌らしいものをのびのびと書いてもらえればと思っています。

*1:まあKDPならわれわれでも出せましたが

*2:ちなみに技術書典シリーズ第1弾は最新JavaScript開発です。

安全なビルドタスクとクリーンタスクを実装するために気をつける点

ビルドシステムを使いこなすのは難しい。この場合「使いこなす」とは、Visual StudioでF5を押してビルドしたり、メニューから「クリーン」を選んで実行する、程度の内容ではなく、ビルドタスクを記述して他のユーザーに自分以外の開発環境でビルドを期待通りに動かす程度には複雑な操作を行うことである。

あるビルドが期待通りに動かない、という問題は、もう少し具体的に挙げるなら、たとえば次のような状態だ:

  1. ビルドが成功していると言われているのに、期待しているアウトプットが無い
  2. ビルドが途中でエラーを起こして終了する
  3. ビルドが完了しない
  4. ビルドが想定外のソースを元に行われて完了する
  5. ビルドが期待通りのアウトプットを生成しているにもかかわらず、失敗と表示される
  6. ビルドは一応期待通りに完了するが、時間がかかり過ぎる
  7. ビルドが期待通りにエラーを出すのだが、そのメッセージが間違っているか、意味が伝わらない

同様に、クリーンアップが期待通りに動かない問題にも、いくつかパターンがある:

  1. クリーンアップで、ビルド出力以外のファイルが削除される
  2. クリーンアップが期待通りにファイルを削除しない
  3. クリーンアップで、期待されている以上にファイルが削除され、次のビルドに時間がかかりすぎる

「ビルドが成功していると言われているのに、期待しているアウトプットが無い」典型例は、ビルド中のあるステップで失敗しているのに、それに気付かずにビルドが続行してしまう場合だ。子プロセスを実行しているなら、その結果を終了コードなどで確認する必要がある。あるいは、コマンドは実行継続中なのに、呼び出し元には処理が戻っていて、その後の処理に依存しているのに、それに気付かずに続行しているのかもしれない。

「ビルドが途中でエラーを起こして終了する」場合や「ビルドが完了しない」場合というのは、ユーザーのカスタムビルドタスクがおかしい場合もあれば、そもそもビルドエンジン(ビルドツール)がおかしい場合もある。ビルドツールもプログラムに過ぎないのだから、そのような問題は当然発生しうるわけで、これらは理解しやすい問題と言える(デバッグの難易度は別論として)。

「ビルドが想定外のソースを元に行われる」というのは、たとえば次のような場合だ:

  • 一旦ソースコードに何かしらの加工を加えた上でコンパイラに解析させるビルドの場合(AOPなどを想定すると良い)、きちんと最新のユーザーソースをもとにAOP処理が行わなれないと、古いソースがコンパイラに渡されることになりかねない、といった例がある。TypeScriptがJavaScriptに正常にコンパイルされていないのに、最後にコンパイルされた時に生成されたJavaScriptファイルをそれ以降のビルドに使って、実行時エラーを起こす、なんてこともあり得る。
  • あるいは、「クリーンアップが期待通りにファイルを削除しない」問題と組み合わさることで、不正に残されたビルドキャッシュから「クリーンなはずの」ソースをビルドしようとして、一貫性のないアウトプットが生成されることがあり得る。

「ビルドが期待通りのアウトプットを生成しているにもかかわらず、失敗と表示される」という状態は、一番分かりやすい例では子プロセスの終了コードのチェックが反転している(0をエラー扱いする)などの場合で、ビルド出力がある以上実質的な問題はないという場合もあるが、続く処理があってもそこで終了してしまうかもしれないという問題がある。

「ビルドは一応期待通りに完了するが、時間がかかり過ぎる」というのは、ビルドそのものが妨げられているとは言えないが、例えば次のような場合に問題になる:

  • ビルドキャッシュをうまく構築できていない。(キャッシュの生成に失敗している、認識できていない、普通にビルドするよりキャッシュの構築のほうが時間がかかる、など)
  • 一回構築するのに長大な時間を要するビルドタスクなのに、クリーンするたびに再実行しなければならない。100MBのダウンロードが、毎回クリーンのたびに実行されるのはまずい。

「ビルドが期待通りにエラーを出すのだが、そのメッセージが間違っているか、意味が伝わらない」というのも、ビルドそのものが妨げられているわけではないが、問題が発生した時に原因の追及が困難になる。

クリーンアップに話を切り替えよう。

そもそも、クリーンアップタスクが難しくなる要因のひとつは、「雑にビルド出力ファイルや中間出力ファイルを消せない」という点にある。ユーザーがデバッグに必要なファイルをとりあえずbinディレクトリにコピーして置いておくのだけど、安易に消してほしくない、ということがあるかもしれない。まともなMSBuildカスタムビルドタスクがbinディレクトリをまるっと削除することが無いのは、そういう理由による。ここをガン無視すれば「クリーンアップで、ビルド出力以外のファイルが削除される」という問題は生じるものの、想定されていない再ビルド結果には、ならなくなる。

「クリーンアップが期待通りにファイルを削除しない」というのは、ビルドタスクの作業を追加して 中間ファイルが新しく増えたことを忘れている、というのがよくある要因だろう。また、そもそもビルドタスクの中で外部ツールを呼んでファイルを生成していたりすると、呼び出すタスクを実装している側も、その外部ツールの実行結果なんて把握していられないこともあり、そうなると一貫しない結果が生じる余地が大きくなる。

「クリーンアップで、期待されている以上にファイルが削除され、次のビルドに時間がかかりすぎる」というのは、たとえばクリーンビルド時にMavenやNuGetの依存ライブラリを毎回ダウンロードしてローカルにキャッシュする作業が毎回発生してはならない、ということである。完全に「クリーン」なビルドがきちんと実現できるのは一番安全ではあるが、毎回ダウンロードまで完了しなければクリーンビルドが出来ないというのは、やりすぎである。

この問題の亜種で、「クリーンアップで消してはならないファイルを消してしまって、それなのに間違ったビルド初期化キャッシュ情報が残っている結果、初回ビルドの時に行ったダウンロードと初期化の処理は行われず、結果的に以降のビルドが失敗する」という例もあると思われる。

…とまあ、いろいろな事情が考えられる。いずれにしろ、ビルドタスクが想定通りに動かない問題に遭遇したら、こんな感じで列挙した問題のいずれかにひっかかっていることもあると思うので、参考になることがあれば参考にされたい。

技術書典3向けXamarin同人誌の執筆者を募集します

あの技術書典が今秋にも行われることになりました。

techbookfest.org

そういうわけで、技術書典2の時と同様に、サークル参加してみようと思います。前回はおっかなびっくりいろいろ手探り状態で進めていたわけですが(それでもTechBoosterなどを見ていたのでいろいろやりやすかったはず)、今回は進めやすいかなと思っています。

募集といっても雑なものなので、前回の募集要項振り返りをもとに、書きたいという方がいらっしゃったら、以下のgithubリポジトリのissueで参加表明などしていただければと思います。

github.com

前回募集していた時はそこまで意識していなかったのですが、前回は結果的にXamarin関係の執筆者としてはかなりの強者が集まっていて、商業クオリティの本どころか現時点で最強クラスのXamarin関係書籍が2冊分も出来て実際に商業化の作業も進んでいるわけですが、わたしとしてはあくまで同人誌として「みんなが書きたいことを気軽に書ける」ことを目標にしたいと思っています。もちろん、ガチガチのクオリティで書いてほしくない、ということは無くて、最強の原稿を書き上げていただければ、可能な範囲で適宜アレンジします。

同人誌を出すサークルを回すための知見もいろいろ共有しているつもりなので、執筆しながらその辺も見ておきたいという人も気軽に参加してもらえればと思っています。

誰もいなければわたしが1人で適当に何か書いて出します(まあそんなことにはならないと思いますが)。

Android Architecture Components for XamarinのProof of Concept実装

表題のようなものをproof of conceptとして作成したので出しました。

www.nuget.org

実質2日程度の成果で、基本的にはまだ使い物になりませんが、Lifecycleの雰囲気を味わうことくらいはできます。あ、Roomのサポートは実装していません。ORMはJava方面でも他にいろいろあるので(いやRoomはORMじゃないけど)、その辺を眺めつつ、Roomは後回しでいいやと思っています。

Android Architecture Components(以下AAC)は、Google I/O 2017で公表された新しいライブラリで、いずれサポートライブラリにも統合されるはずのものです。ここではAACがどんなものであるかについての説明はしません(既にさまざまな 解説が 出ているので)。

ただ、単なるAndroidライブラリであればバインディングを用意するだけなので瞬殺なのですが、AACにはapt(annotation processor)に依存する部分があるので、そこを何とかして代替しなければいけませんでした。実際にわたしが作ったのは以下の2点です。

ライブラリのバインディング

基本的には普通のAndroid Bindingライブラリです。

ここでも書かれていますが、android.arch.lifecycle_runtime.aar中にAndroidManifest.xmlが含まれており、その中でカスタムproviderが定義されている仕組みです。これを参照しているアプリケーションではmanifest mergerが必要な要素を追加してくれます。(Xamarin.Androidには簡易manifest mergerが実装されています。)

java.lang.Enumを使っている部分については、C#でAttributeとして指定できないため、JavaEnumに対応するC#Enumを定義し、それに対応するimplicit operatorと、enumをパラメータとするAttributeのコンストラクタを定義すればOK…と思っていましたが、実際にはenumにすることは出来ませんでした。enumにすると、後でMSBuildタスクの中でcecilで値を取り出すときに、そのenum型がデスクトップ上でインスタンス化出来なければならないところ、モバイルプロファイル上でビルドしてしまったenumを復元することはできなかったためです。仕方ないのでenumはあきらめてintにしています。早いとこenumは滅ぼして@IntDefに相当するIntDefAttributeに移行したい(ま、Googleがまともなannotations.zipを提供していないので難しいですが)。

aptツールの代替手段

基本的には、NuGetパッケージを参照するかたちにしておけば、このライブラリを参照しているアプリケーション プロジェクトのビルド時に、NuGetパッケージに含まれるMSBuild targetsが自動的に追加されるので、BeforeTargets/AfterTargetsを利用してビルドに追加タスクを差し込むことが出来ます。Xamarin.FormsのXamlCタスクがコレで出来ているはず。

LifecycleAdapterの自動生成

Lifecycleについては、以下のようなコードに対して:

class TestObserver : Java.Lang.Object, ILifecycleObserver
{
    [OnLifecycleEvent (OnLifecycleEvent.OnAny)]
    public void OnAny (ILifecycleOwner owner, Lifecycle.Event evt)
    {
        Console.WriteLine ("OnAny invoked.");
    }
    [OnLifecycleEvent (OnLifecycleEvent.OnStop)]
    public void OnStopped ()
    {
        Console.WriteLine ("OnStopped invoked.");
    }
    [OnLifecycleEvent (OnLifecycleEvent.OnStart)]
    public void OnStarted ()
    {
        Console.WriteLine ("OnStarted invoked.");
    }
    [OnLifecycleEvent (OnLifecycleEvent.OnStart | OnLifecycleEvent.OnStop)]
    public void OnStartedStopped ()
    {
        Console.WriteLine ("OnStartedStopped invoked.");
    }
}

以下のようなコードが自動生成されてビルドされる必要があります:

class TestObserver_LifecycleAdapter : Java.Lang.Object, IGenericLifecycleObserver
{
    readonly TestObserver mReceiver;

    public TestObserver_LifecycleAdapter (TestObserver receiver)
    {
        this.mReceiver = receiver;
    }

    public Java.Lang.Object Receiver => mReceiver;

    public void OnStateChanged(ILifecycleOwner owner, Lifecycle.Event evt)
    {
        mReceiver.OnAny(owner, evt);

        if (evt == Lifecycle.Event.OnStart)
        {
            mReceiver.OnStarted();
        }
        if (evt == Lifecycle.Event.OnStart)
        {
            mReceiver.OnStartedStopped ();
        }
        if (evt == Lifecycle.Event.OnStop)
        {
            mReceiver.OnStartedStopped ();
        }
        if (evt == Lifecycle.Event.OnStop)
        {
            mReceiver.OnStopped ();
        }
    }
}

やっていることは、ILifecycleObserverを実装するクラスを全て洗い出して、そのLifecycleAdapterを定義して、その中のOnStateChangedの中で、関連属性の付いているものを呼び出すようにコードを生成するだけです(と、理解していますが、もしほかにもやるべきことがあったら教えてください)。

どこでLifecycleAdapterを自動生成すべきか

やり方はいくつか考えられます:

(1) コンパイル前にソースコードを自動生成し、まとめてビルドする

Roslynを使ってプロジェクトを解析するやり方。これはどの時点でタスクを挟み込めるのか見極めが必要になるし、セマンティック分析まで行ってからコード生成しないといけないので、それなりに重いです(ビルドを二重に行っているような状態になるので)。

(2) コンパイル後にバイナリを解析しソースを自動生成し、別途ビルドする

Csc後に出力アセンブリをロードして、バイナリを解析し、コードを生成した後、別のコードファイルを生成し、参照を追加した上で別のアセンブリコンパイルします。実装は比較的素直になりますが、コンパイルは(生成コードは小さいとはいえ)二度発生します(キャッシュしておけば回避できるでしょう)。また、アプリケーション上のinternalクラスもきちんとサポートするためにはInternalsVisibleToAttributeを追加してビルドすることになるでしょう。そして、GenerateJavaStubsタスクがこの後に実行される必要があるので、順序を気にしなければなりません。

(3) 実行時にコードをクラスを生成する方式。Reflection.EmitやExpression Treeやらを駆使して動的にこのクラスを生成するアプローチですが、これはAndroid Architecture Componentsと組み合わせることは出来ません。なぜなら、Architecture ComponentsのLifecycleRuntimeTrojanProviderはJavaのLifecycleObserverインターフェースの実装(か何か)を探索して、それをフレームワーク側から呼び出しているため、Java Callable Wrapperを生成する必要があるからです(これはXamarin.Androidのドキュメントで「出来ない」と明記されている制限事項)。またAOTビルドと相性が悪いのもあまり好ましくないでしょう。

以上のように考えて、結局(2)を採用したわけですが、Xamarin.Androidチームが愚劣な設計を行っていて、生成されるJavaコードのパッケージ名はmd5でmanglingされてしまう問題があって、これがあるせいで、別のアセンブリにあるクラスは同じネームスペースに含まれていてもJava上では違うパッケージになってしまいます。これはAndroid Architecture Componentsの実行時型処理を根本的に台無しにしてしまうし、Javaパッケージ名をC#から推測できるようにする類のライブラリにとって害悪でしか無いので、生まれる前に消し去る必要があります。いずれにせよ、現状ではC#で書いているすべての型について、[Register(javapackagename.ClassName)]を追加する必要があります。

ちなみに、なんでそんなmd5hashを使うような設計になっているかというと、Javaアプリと違って.NETでは1つのプログラム(エントリポイントから参照・実行される複数のアセンブリということにしておいて)で複数のアセンブリが同じ名前空間と同じ名前の型をもつことが許されるので、JCWでは識別性のある名前にしないとサポートできない…という理由によります。そんなのサポートしなければいいんですが。library package developersを救ってextensibility developersとend user developersに迷惑をかける仕様じゃ意味ないし。

特に最後の問題は、Xamarin.Androidのまともなユーザーであれば絶対に採用し得ないレベルのものなので、コレはproof of conceptです。(1)にシフトすれば解決する問題ですが、md5 manglingは滅ぶべきなので、(2)で通したいところです。パフォーマンスも全然違うだろうし。

あと、MSBuildのAfterTargetsなどで指定している対象が、(暗黙的に)パブリックなターゲットではないので(名前が_で始まっている)、Xamarinが内部実装を書き換えたら動かなくなる可能性があります。この辺はpublicなMSBuildプロパティとしてGenerateJavaStubsDependsOnとかXamarin.Androidの中に作ってしまえば良いことです(まあ開発者だから言えることですが)。

そんなわけでいろいろalpha qualityというよりproof of conceptですが、試したい方はどうぞ。

補遺: パッケージング

NuGetパッケージング プロジェクトはなぜかビルド時にNullReferenceExceptionを発生させていたのと、MSBuild targetsをうまく扱えなかったのとあって、今回は使いませんでした。

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アプリケーションの実践的なデバッグ・トラブルシューティング手法などが紹介されるセッションに興味があるわけですが、特に裏番組はさすがに無理だし惜しい…!という気持ちです。