2017年とは何だったのか

気がついたら2017年ももう終わろうとしています。1年間、ずいぶん自分の意図とは違うことをやっていたような気がしていて、違和感の正体を探る意味でも振り返りが必要であるように思いました。

Xamarin系同人誌(と商業出版)

自分でも振り返っていて驚いたのですが、技術書典(2)の開催に合わせて自分でXamarin同人誌をやろう、と言い出したのは今年の初頭でした。2回(超を入れたら3回)の技術書典に合わせて、幸いにも延べ10人ほどの執筆者に集まっていただいて、Essential Xamarin 陰/陽、Extensive Xamarinの3冊を出すことが出来ました。

特に当時まだXamarin本インフレ(!)が起きていなかった頃に頒布したEssential Xamarinは大成功で、さらにインプレスR&Dさん経由で商業出版のかたちでもリリースされ、達人出版会さんでも購入していただけるようになりました。実は技術書典3で出したExtensive Xamarinも同様に商業化の作業が進行しています。正式に決まったらまた告知するつもりです。

どちらの書籍も、同時並行で同人誌版が販売されている状態で、実際技術書典3ではブースで商業版も紹介しているにもかかわらず、同人誌版がある程度売れていました。Essential Xamarinは超技術書典に合わせてかなり多く刷ってあり(超書典は参加者が同人誌を見に来たイベントではなかったので単に売れなかったわけです)、Extensive XamarinはXamarin本インフレの渦中で頒布したこともあって前回ほど売れていないので(印刷代がぎりぎり回収できていないライン)、在庫が豊富にある状態です。

xamaritans.booth.pm

基本的にこの活動は赤字前提で進めているのですが、今のところ割ともったいないなーと思っているのが輸送費で、イベントで段ボールふた箱を往復すると数千円かかってしまうので、ちゃんと数を調整しないとなあと思うところです。赤字前提と言っていますが、コストは予測できている範囲ではそれなりに考えていて(印刷所の早割が使えるように締切も設定してあるし…!)、まあ赤字になっていたのは知識・経験不足ゆえですね。

頒布数については、商業版がどれくらい売れているのか、まだ数字を受け取っていないのでわからないのですが、同人版が余っている中で商業化もするという、販売戦略としては悪手を繰り返しているので(商業化とくにオンデマンド出版に関しては、プロフェッショナルの編集が入るというのと著者を商業プロデュースできるという以外のメリットは無いです)、これはアンチパターンなので真似しないほうがいいと思います。利益を出す目的で活動をしたいなら、原稿料をもらえるオンライン記事でも書いていたほうが良いでしょう。

わたしの場合さらに特別な事情として、技術書典当日はイベント本体の運営側スタッフとして稼働しないといけないこともあって、ブースに来ていただいたお客さんと話をする機会が無いんですよね(そこはサークルでお手伝いいただいた共著者他の皆さんに助けていただいています)。来年4/22には技術書典4が開催されますが、どう対応するかまだ決めかねているところです。

Xamarin.Forms訳本と監訳読書会

今年一番自分のリソースを吸い取られたのがこの作業でした。昨年の後半から取り掛かっていたことを考えると、たかだか1600ページ程度(!)の本にとんでもない時間がかかっていますね…(後編が未だに出ていないのはそれなりの理由がありますが、監訳作業自体は9月にはほぼ終わっています)。監訳作業と並行して「監訳読書会」を行ったのが、特筆すべき活動だったと思います。

監訳読書会は、開催して良かったと思っています。毎回10人max程度で募集して、5人以下で進めたり10人を超えたりもたまにありましたが、きちんと議論できる場面が多々あり、かなり読み込んでコメントしてくださった方も数名いらっしゃったので、とても助かりました。

監訳読書会にはさらにチャレンジがあって、原則としてさまざまな勉強会会場としてオフィス等にお邪魔させていただいて開催する、というルールを自らに課していました(上巻と下巻それぞれについてなので、上巻でも下巻でも会場に使わせていただいた会社さんはいくつかあります)。これは特定企業と癒着するようなXamarinのPR活動にならないよう気をつけてのことです。ただ、この縛りは割と厳しくて、毎回どこにしよう…と悩まされる問題でした。幸い多くの方から「うちを使ってもらってかまわない」というお申し出をいただいたので、開催に困るということにはなりませんでした。後半は何度か貸会議室を使っていたのですが、「早く決めないと来週開催できない…! 開催場所で迷って時間を浪費していられない…! 自分で会議室を借り早く募集しよう…!」となっていたためです。

また、勉強会を開催すること自体の負荷とは別に、毎回日経BPさんから訳本の草稿を印刷していただいて送っていただいていたのですが、これが膨大な紙の量になっていて(1000ページ弱の本を10人分刷ることを考えてみて下さい)、これを印刷して送っていただくというのはけっこうな労苦とコストだったはずです。可能なら電子データで進めたいところですが、未出版の本で出版社から見たら得体のしれない参加者に配布となると、なかなか難しいところでしょう。

監訳作業自体には、藤原さん(@yfakariya)に加えて途中から猪股さん(@matarillo)にも参加していただいて、後半特になかなか動けなかったわたしの手が及ばなかった部分を多大に助けていただきました。監訳メンバーとしては外れていますが、田淵さん(@ytabuchi)にも勉強会の開催などでお助けいただきました。

この本に関する詳しい報告は、下巻が出たらまた改めて出そうと思います。いろいろ明らかにしてかなければならない話もあります。

勉強会等のセッション登壇

今年は前年に「しゃべりすぎた」と思っていたこともあって、あまり勉強会に出てしゃべることはしない予定だったのですが、結果的に4回しゃべっていました。

Xamarinを支える技術2017 - これはde:codeという日本マイクロソフト(そろそろ半年くらい出社していない)のカンファレンスの一室で行われたチョークトークとは名ばかり()のセッションだったのですが、これまでmonoのランタイムにきちんとフォーカスを当てた話をしてこなかったので、Xamarin製品に使われている技術がどういう歴史を経て実現できたのか(インタープリタJIT、AOT)、これからどういう展開が期待できるのか(特にwasmまわりなど)、といった話をしました。これは割とアリだったかなと思っています。有償イベントだし社員としてしゃべるセッションでもあったので、ちゃんと前向き()な話にして、それなりにストーリーを用意していった記憶がそこはかとなくあります(チョークトークとは何だったのか…)

Generics on Xamarin products - 6月のジェネリクス勉強会、アツかったですね。クロスボーダーな勉強会で、Xamarinにもいろんな印象をもって参加してきた人が多かったはずで、きちんといろんな言語のバックグラウンドの人に価値のある話を提供できるようハイリョしながら(ホントか?)今年一番気合を入れて用意したのですが、.NETクラスタ以外から「面白かった」という反応が得られたのが嬉しかったですね。この時はランタイムのコードの細かいところまで読みながら資料を書いたので、自分でもめっさ勉強になりました。

シンガポールのmonkeyfestは主にひらのさんと観光に行ってきましたという感じですね。無事海外セッション登壇デビューできてナニヨリだと思いました(!) わたしは台湾COSCUPやMS台湾のカンファレンス以来でちょくちょく英語でしゃべっていたので、まあちょっとひさしぶりかな?という感じでした。内容も昔の仕事プラスちょこっと今の仕事という感じでライトだったし。イベント自体は…まあ技術指向のイベントにならない限り、二度と行かないと思いました。

名古屋ではEmbeddinator-4000の話をシュッとする予定だったのですが、時間が余っていたということもあって結果的にだらだらと説明してしまったので、来年DroidKaigiで最新情報に合わせてシュッとした話をしようと思います。客層が全然違うので、内容はだいぶ違うものになると思います。名古屋の方とお話できたのは良い機会でしたね。(12月にも行ったのですが、即日帰らないといけなくてちと残念でした。)

2018年に向けて

2017年は(主に米国の事情を見つつ)全世界的に高潔に生きようとする者を嘲笑するような1年だったと思いますが、なんとか人としての道を違えずに、筋を通して生きて来られたと思います。

2017年はまわりに無職が増えて(まあ摩擦失業が大半ですが)、わたしもはよ引退したいと愚痴りながら過ごす1年だったのですが、来年こそはできるだろうか、と考えながら過ごすことになると思います。年金暮らししたいなあ。(できません)

引退前にいろいろ自分だけが知っていて伝えるべきことを、きちんと伝えていかないとなあ、と思うのですが、そのためにリソースを費やしていると、次に向けて本来自分が行うべき活動ができなくなってしまうというジレンマがありますね。

いずれにしろ、来年は主軸を音楽ソフトにシフトしていくつもりです(毎年言っている気がする)。何だかんだで素人ですし、目立たない活動になっていくかなと期待しています。技術書典もできればそっちの方向で出していきたいと思っています。今のサークルというか既刊をどうするかというのが当面の問題ですね。

そんなわけで来年もどうぞよろしく。

Kotlin/Native解説 -at- C93:Androidモダンプログラミング

最近twitterなどでもKotlin/Nativeについていろいろ発言していたので何となく察していたという人もいるかもしれませんが、C93(冬のコミックマーケット)のTechBooster新刊「Androidモダンプログラミング」に、今年の春にリリースされて秋にkotlinconfで注目を浴びたKotlin/Nativeについての解説記事を35ページくらい書きました。

techbooster.github.io

たぶん世界で公式サイトに次いで2番めに詳しい解説になっていると思います。

Kotlinプログラミングに関しては素人だし、Kotlinそのものについての解説は1ミリもありません。Kotlinでネイティブアプリを作れるとはどういうことなのか、という部分にフォーカスした内容になっています。これAndroidプログラミングの本ということになっているけど、Android開発についてはKotlin/Nativeはほぼ関係ないと思っていいです。(詳しくは本編で)

詳しく書いていないのはXcodeを使ったiOSプロジェクトの開発方法などですが、年末に日本語で詳しく書かれた記事がいくつか出てきたので、うまい具合に補完し合えていそうに思います。

Kotlinのような「言語から作られた開発エコシステム」が先にあるような状況から、ネイティブアプリケーションを作れる仕組みを用意する、というストーリーラインは、たぶん割といろんな言語開発環境で共通していて、われわれも普段monoをその用途で使っているので、近いところにいるわけですね。とは言ってもKotlin/NativeはLLVMそのもののマナーに乗っかっていて、うちはmonoランタイムをLLVMの流儀に合わせたり、そもそもLLVMを使わずにランタイムとDLLを実行時に解凍してロードするだけ、みたいな仕組みもあったりと、その具体的な手法は一様ではないところです。

Kotlin/NativeはKotlin/JVMからある程度うまいこと切り離されていて、コンパイラJava VMで動作するものに、KotlinのIRからLLVM IRを生成する(そのためのKotlinコードがJVM上で動作するコンパイラプラグインできるように容易されている)のが、われわれ仕組みを眺めたい側としては面白いポイントのひとつですね。

Kotlin/Nativeでわたしにとって一番印象的だったのは、cinteropツールを使ったC APIの自動バインド機構ですね。JavaCPPやJNAeratorのようなものですが(わたしがPInvokeGeneratorでちょっとやってみようとしていることでもある)、仕様が詳しく解説されており、生成されるコードがわかりやすくなるように出来ています。

Kotlin/Native、まだIDEを使ってちゃんと開発できる状態にはなっていないですが、荒削りで勢いがある分野に飛び込んでコントリビュートしたり広めたりするというのはとてもいい経験になると思うので、興味があるという人はぜひチャレンジしてみるといいと思います。(わたしは来年はもっと別の方面に進む予定なので、興味深く眺める程度にしておくつもりです。)

2017年の終わりに感謝する作品集

クリスマスから年末にかけて書いている恒例のエントリです。atsushienoが今年よく聴いた・見た・使った作品を書き連ねて感謝の念を示すものです。例によってatsushienoが「今年知った」ものであって「今年公表された」作品とは限りません。(2016年版

音楽から。

www.youtube.com

今年はたまたま今まで未チェックだったdiverse systemで、とくにFeryquitous作品を聴いていた1年でした。曲提供されているSennzaiアルバムArrêter le tempsは綺麗に作られた曲が多くて、よく流していました。

Feryquitous/Sennzaiどちらの方も冬コミ(C93)1日目に新作が出るようなので買いに行こうと思っています(1日目は技術系同人誌と同じ日)。なんかRayark作品でよく見かけた作曲陣だなーこういうところに集まっているんだなー、というのが見えた1年でもありました。

irui.diverse.jp

www.nicovideo.jp

diverseで出たアルバムで他に印象に残った曲はAD:HOUSE 6のIndigo Coralとかですね。l8 r2..ed.c.<b> c4.c<b.>c.<f g4.gf.e.e^ 4.ed.g.<b^ 8.>cree.f.g f4.fe.b.a^ 8.e.d.cg.f^ f4f.e.e^ e1みたいなメロディとか好物ですね(追っかけていて気づいたけど構成音が全部cdefgabだこれ)

UTAUカバーですげぇ…!ってなったのがNumber Bronseの薄ら氷心中ですね。

www.nicovideo.jp

UTAU方面を真面目に追いかけていなかったので(そんな慣習は無かった)、ここ1,2年ほどでどのように人間らしく歌う打ち込みが簡単になってきたのか分かっていないのですが、けっこうこういう作品が出てきているようですし、シンセサイザーが生楽器の制約を解消したように歌唱合成が肉声の制約を解消する時代が来るのを楽しみにしている勢です。まあそれはそれとして人間のほうを好んで聴いていますが。

次は音楽ではなく動画として…隠された物語が明かされたDeemo 2.0のエンディング。これについては1月に詳しく書いたので繰り返すことはしません。この動画のえらい再生回数とコメント数…(なお、音楽はずっと前から知っていたので選外です)

ソフトウェアの分野では、GPL化したVSTですね。

https://sdk.steinberg.net/viewtopic.php?t=282sdk.steinberg.net

Linux環境でも他の環境と遜色なく音楽制作しようと思ったら、もうWebAudio/WebMIDIにシフトするかVSTを滅ぼすしか無いじゃない!…と思っていたのですが、VST3.6.7から?GPLとのデュアルライセンスになり、自由なソフトウェアからは自由に利用できるようになったのです(そのへんの話は前回のpostで書きましたね)。まだろくに使っておらず、正直まだ使える環境やVST pluginはあまりないんじゃないかとも想像していますが(主に試しているTracktionのVSTプラグインが全然使えていないので)、この辺の意識を転換できたことはかなり嬉しく思っています。これでWindowsに戻らなくても何とかやっていけるようになるかもしれん…

VST3がGPL化されたことを受けて、JUCEコミュニティでもLinux版を出してほしいというリクエストが出ていたりします。JUCEとTouchDesignerはわたしが使ってもいないけどLinux版が出てほしいなあと思っているツールof the year賞受賞作品です(!?)

音楽ソフト関係はlibsoundioとかWaveFormとかいろいろ新し目のものを知ることが出来て、これまではMIDIでしか考えることが出来なかったのが、オーディオまわりもちゃんと勉強する準備が出来てきたかなあという気持ちになってきました。腰を据えて勉強するならやはり退職するしかないかなあという気もしていますが来年も少しずつ詳しくなっていきたいと思います。

…時間的にこんなところでしょうか。来年もまた素晴らしい作品に出会えることを楽しみにしています。

12/25追記: 書籍について書こうと思っていたのをすっかり忘れていた! 今年はダントツでこの2冊でしょう:

gihyo.jp

gihyo.jp

Androidの内奥について、世界でも最先端レベルの書籍だと思います。これ、今年の初頭に独立エントリで書いて載せようと思っていたのですが、途中から多忙になってすっかり機を逃してしまったので、このタイミングで載せておきます。文体変わっちゃうから切り取り線の後は別物だと思って読んで下さい。


非常に刺激的な本で、わたしのようにAndroidアプリケーションをほとんど書かなければそもそも技術書も滅多に読まないAndroid開発者でも、楽しんで読むことが出来た。これを肴に勉強会というか雑談会をやったら面白いんじゃないか、と思っていたのだけど、先日わたしが参加しているまったりAndroid Framework Code Readingに行ったら、案の定この本を楽しんでいる人がたくさんいたので、いろいろ語りたい人がいたら次回参加して語ってほしい(次回がいつになるかはわからないが)。

この本がどういう位置付けなのかは、著者の有野氏が冒頭で自ら明かしているが、Androidのアプリケーションのライフサイクルや、Viewのコードやリソースから描画までの処理モデルといった、フレームワーク部分の詳細を理解することで、実際のコーディング上の問題を解決するためのものだ。一方で、Androidフレームワークについて、ここまで詳細かつ親切に解説している書籍は無いだろう。(日本語で読めるものとしては、邦訳が出版されている「Androidのなかみ - Inside Android -」がある。原著はAndroid 2.x時代のもので、邦訳で追記された内容も4.x時代で、やや古い。)

http://www.personal-media.co.jp/book/comp/288.html

これが中国語圏まで探索範囲を広げるといろいろある。中国語圏には、HTC、AsusHuawei、Xiaomiなど、多数のAndroidハードウェア企業が存在し、低レベルの開発に関心の大きい開発者が多いというわけだ。わたしの手元にある書籍だとこういったものがいくつかある。

ただ、これらの書籍の内容は(わたしは中国語はネイティブからは程遠い話者であることを考慮してもらいたいとして)、かなりの割合で雑な「AOSPからのコードの引用と追跡」が含まれていて、自然言語の解説は時としてびっくりするほど薄い。「ここを追いかければいいよ」というレベルのポインタで終わっているところも少なくない(濃密な解説が書かれている部分もあるだろうけど)。

そこまで考慮すると、この本はコードが少なめになっており、そのぶん各部分の意図が丁寧に説明され、それらが明確な意図(たとえば「どうやって60FPSでListViewを描画するのか」といった目的)を導線として読み進められるようになっている。これは有野氏が自ら「同じレベルの内容を扱う類書に比べると、本書は極めて平易に詳しく解説が書かれています」(P. iv)と書かれているとおりだ*1

第I巻はGUIシステムに、第2巻はActivityとアプリケーションのライフサイクルを中心にまとめられている。GUIに興味のない人は第2巻だけ読むことも多分できるだろう。

この本のもうひとつの魅力は、解説の内容がAndroidにとどまらないところだ。もともと著者の有野氏はMicrosoftWPF関連の開発に携わっていたようだが、GUIシステムにはプラットフォームやフレームワークを問わず共通している部分が少なくない。「他所を見ておく」「他の実装アプローチの可能性を探っておく」ことは、歴史から学ぶという観点でも有意義だ。

法律学に関する書籍が「現行法の解釈」だけを「アレはOK、コレはダメ」みたいに書かれていたら、それは学問として失格である*2。アプリケーションの設計方法について、さまざまなアプローチがあり得るのに「MVVMというのがある」「MVVMではこうする」「コレを使えばいい」とか書いているだけの文章が、退屈でエンジニアの知性を刺激しないのも同様だ。さまざまな部分で「なぜ」Androidの仕組みはこうなっているのか、という説明が含まれているのを見れば、「なぜAndroidはこうなっていないのか」ということも理解できるようになる。かもしれない。なぜGUIシステムはQtやGtk+を使わず独自に構築されたのか。なぜpure Javaなのか。この本は、われわれが「何でだろう?」と思うことにいろいろ答えてくれる。

第1巻の内容は、GUI以外の部分のざっくりとした解説(そのいくつかは第2巻の内容になっている)、タッチイベントの伝播の仕組み、UIスレッドといわゆるメッセージループの挙動、レイアウトリソースからの論理的なViewツリーのモデル構築とそのレイアウト、レイアウトされたViewからOpenGL ESの描画命令表現に変換する処理、OpenGL ESからハードウェアコントローラーへの命令出力…という流れで、グラフィックス処理を行うために必要な処理の大部分を解説し、最後にDalvikバイトコードの実行環境(ART)についても説明してある。

第2巻の内容は、AndroidのActivityはどのように生まれそして死んでいくのか、そのライフサイクルを追うために、アプリケーション開発者も意識するActivityのインスタンス化と複数のActivityをスタックに積む仕組み、それらを生成するためにやり取りされるIntentとその解決のために必要なアプリケーションのパッケージ情報の処理、逆にアプリケーションをkillするために生存優先度を計算するOOM Killerの機構、それらを踏まえた上でひとつのアプリケーションを開始から終了まで実行するZygoteプロセスやその中で動くActivityThread(のmain()メソッド)の流れ、そしてfork元のZygoteプロセスを立ち上げるまでのAndroid(というかLinux kernel)のブートプロセス、などが説明されている。

*1:氏が中華圏の書籍まで目を通していたかは知らないが

*2:山形浩生氏も「CODE」の訳者あとがきで書いているように

libsoundio-sharpとPInvokeGeneratorについて

このエントリはC# Advent Calendar 2017の7日目のエントリです。6日目のあめいさん @amay077 のエントリからのバトンを引き継いでいます。

まえがき

.NETエコシステムに圧倒的に足りないもののひとつが、クロスプラットフォームサウンドAPIです。サウンドAPIは伝統的にプラットフォーム固有のものであり(例: NAudioCSCore)、SharpDX)、これらはクロスプラットフォーム アプリケーションで使うことはできません。これではC#クロスプラットフォームサウンド系アプリケーションが書かれる日は未来永劫来ないでしょう。

クロスプラットフォームサウンド系アプリケーションなんてあるんでしょうか? あるんですよ。本格的なのが。

Bitwig Studio 2.2: Bitwig Studio 2.2

Renoise 3.1: Renoise 3.1

Tracktion Waveform8: Waveform8

これ全部Ubuntu 17.04のデスクトップで動いている最先端の商用DAWです(Renoiseはtrackerというべきか…?)。

DAWの多くはVSTをサポートしているのですが、VSTWindowsMacにしか存在しないので、クロスプラットフォームにしても無駄です。Linuxでは代わりにDSSIとかLADSPA、Lv2 (LADSPA v2)が使われますが、独自のエコシステムになっています。…と言われて納得しそうになったでしょう?

VSTは今やフリーソフトウェアなんです。GPLv3で公開されているんです。 https://github.com/steinbergmedia/vst3sdk

一方でVSTクロスプラットフォームで開発するには、JUCEなどに頼りながらC++で行うのが一般的でしょう。これがC#で出来るようになったら開発が捗ると思いませんか? アレ? あんま思わねーな…まあでもUnityで作りたい、みたいな話は、定期的にForumに上がってくるみたいですね。

例によってVST.NETのようなプロジェクトはWindows onlyなので、クロスプラットフォームで実現できるような基盤がほしいところです。vst3sdkのC# wrapperがほしいところですが、それ以前にVST3で生成したオーディオストリームを再生する手段も無いのでは、何を作っても足元がおぼつかないことになります。

というわけで、まずはクロスプラットフォームのオーディオI/O APIから作っていきましょう。

クロスプラットフォームオーディオAPIの構築方法

クロスプラットフォームAPIを構築する方法はごく大まかに分けて2種類の方法があります:

前者は全体的な作業量が多くなりますが、もしプラットフォーム ネイティブAPIを低レベルで呼び出す必要が生じた場合には有利な選択肢です。また、通常はネイティブAPIのライブラリを自前で提供する必要がないので、マネージドライブラリのみを配布すれば良い可能性が高いです。問題は、オーディオI/OのAPIはプラットフォームごとに全然違うので、これらの違いを吸収するようなライブラリは設計も実装も難しいということです。

後者はラッパーをひとつ作成するだけで済むので手軽であり、またクロスプラットフォームAPIの設計にはそれなりの知見が集まっているものなので、高度な機能に依存することでもない限り、そこに便乗したほうが安心感があります。一方で、足りないAPIやサポートされていないプラットフォームがあった場合は、あきらめるか、ネイティブライブラリのほうに手を加える必要が生じてきます。

もっとも、これらは複合的に構築できます。一部のプラットフォームではクロスプラットフォームAPIのラッパーを使い、一部のプラットフォームではプラットフォーム固有のAPIを使用する、といった組み方も現実的でしょう(特にUWPに対応する場合)。

いずれの場合も、ネイティブライブラリを呼び出すにあたっては、そもそもC#のP/Invokeで対応できるライブラリであることが求められ、対応できないものは切り捨てるしかありません。C++のライブラリであれば、CのラッパーAPIを自前で用意するか(作業量が増える)、CppSharpなどのフレームワークに依存する(中間レイヤーが増え、さらに対応可能範囲が狭まる)というややこしい対応が必要になります。(ここでmanaged C++とかC++/CLIを持ち出すと、クロスプラットフォーム対応とは何だったのか…という話になってしまうので割愛します。)

C/C++クロスプラットフォーム オーディオAPIの選択肢

C/C++クロスプラットフォーム オーディオAPIは、多くはありませんが複数存在しています。

  • portaudio: Cで実装されたクロスプラットフォーム オーディオAPIで歴史が古いものはportaudioでしょう。Audacityで使われており、デスクトップ プラットフォームとしてはWindwos, Mac, Linuxで動作します。やや歴史が古いので、OSS (open sound system)などもカバーしています。
  • SDL: ゲーム開発用のライブラリで、主にグラフィックス用だと思うのですが、オーディオAPIも含まれていて、対象プラットフォームは幅広いので、クロスプラットフォーム オーディオの話題でもしばしば登場します。
  • OpenAL: 3Dオーディオ用のAPIです。一般的なオーディオI/OのAPIというわけではないのですが、Android/iOSも含め、守備範囲が広いので、クロスプラットフォーム オーディオAPIの選択肢としてよく挙がってきます。最近だとkotlinconf-spinnerでも使われていましたね。
  • RtAudio: STK (the synthesis toolkit)で使われているライブラリです。RtMidiも含めSSM (single source module)なのが特徴でしょうか(オーディオとは関係ない話ですが)。
  • JUCE: ROLIを中心に開発されており、VST開発で特に重宝されているライブラリです。

いくつかはthe horror of audio outputというblog postに詳しく悪口が書かれている(!)ので、読みたい人は参考にしてもいいかもしれません。3年前なのでいささか情報が古いですが、どのライブラリも古いのでそれなりに今でも当てはまることでしょう…

libsoundio

これらに比べると、libsoundioは比較的新しいライブラリです。libsoundioのサイトでは、portaudioやRtAudio、JUCEなどとの比較を載せています。Windowsではwasapi、MacではCoreAudio、Linuxではjack、ALSA、pulseaudioと、必要最小限のバックエンドをサポートしています。多様なチャネル形態のサポートと、オーディオデバイスの接続イベントなどが取得できるのが、モダンなところでしょうか。

libsoundioのサイトが比較ページで毎回のように挙げている「短所」は、ASIOに対応していないことです。libsoundioのページでは、ASIOよりwasapiのほうが優れているからサポートしないんだ、ということを明言しており、実際にそのような意見が他でも見られます。…と、それだけなら単純なのですが、ASIOが無いと商用サウンドカードの多くが困る、みたいなissueもあったりして、この辺は今後もどうなるか分からないところです。(作者はLinuxで生きているようなので正直どうでもいいのだろうと思います。わたしもLinuxで生きているので正直どうでもいいです…)

もうひとつのlibsoundioの欠点は…というか、ほぼ全てのクロスプラットフォーム オーディオAPIについて言えるのことなのですが…モバイル プラットフォームのサポートがほぼ存在していないことです。iOSはCoreAudioなのでもしかしたらビルドするかもしれませんが、AndroidはOpenSLESやAAudioを使うことになるので、実装は全く存在していません。

libsoundioのさらにもうひとつの懸念点は、開発者の主要なプロジェクトがzig-langであって、libsoundioやその利用事例である自作DAWではなくなっていそうだということでしょうか…。まあいったん作ってしまえばしばらく寝かせておける類のライブラリではあります。

libsoundio-sharpの実装

そういうわけで(?)、今回はlibsoundioのC#バインディングlibsoundio-sharpを作ることにしました。

github.com

ちなみに以前にportaudio-sharpというプロジェクトも作っているのですが、実用例が何一つ無いままに放り投げている状態です(!)。今回は応用事例まで作れるといいなあ…(時間切れで出来ませんでした)

P/Invokeレイヤーの完全自動生成

libsoundioはpure C APIです。これはC#を含む他言語から非常にバインドしやすいものになっていると言えます。のはずなのですが…少々トリッキーなことをやることにはなりました。

C APIの呼び出しなので、libsoundioに手を加えること無く、P/Invokeだけで全て実現しました。さらに、libsoundio-sharpでは、P/Invoke部分は、自作のclangバインディングnclangを利用して完全自動生成しています。もちろん、完全自動生成するために、nclangのサンプルとして作り置きしてあった自動生成ツールPInvokeGenerator.exeには、いくつか手を加えました。PInvokeGeneratorを利用したのは今回が初めてではありませんが、これまでは生成後のファイルに手作業でいろいろ修正を加えて利用していました。今回は生成後のファイルを無修正で使用しています。

ひとつ明確にしておきたいことがありますが、ネイティブライブラリのC#バインディングの作成というのは、C APIのP/Invokeだけ定義すれば終わり、ということにはなりません。それならCでコーディングしているほうがマシです。実のところPInvokeGeneratorでは、P/Invokeメソッドを含むクラスはinternalで定義されます。生成された(C APIをうまいこと包摂した)C#らしいAPIを定義して、初めて実用的なバインディングAPIが出来ると考えるべきでしょう。

さて、P/Invokeレイヤーを完全自動化なんて出来るんでしょうか? 以降は実現を妨げそうなメソッド定義を各論的に取り上げていきます。

refになる引数、outになる引数

libsoundioの出力ストリームへの書き出しを行う関数は、次のように定義されています:

int soundio_outstream_begin_write (
    struct SoundIoOutStream *outstream,
    struct SoundIoChannelArea **areas,
    int *frame_count)

areasは処理結果を格納するポインタへのポインタで、frame_countは入力値が参照され、かつ値が代入されて返される、C#でいうところのrefで修飾された引数です。areasのポインタの示す先には、SoundIoChannelArea構造体が(ここでは定義が見えない)「チャネル数」の分だけ含まれています(毎回メモリ確保されるのではなく、デバイス情報を格納した固定アドレスが返ってくるのだと思います)。Cでは割とよくあるスタイルのAPIですね。類似のパターンとしては、新しいオブジェクトを生成する関数が、引数で渡されたポインタへのポインタに、新しく生成されたオブジェクトを格納したりすることがありますね(戻り値はエラーコードに使われたりとか。int foobarlib_create_foo(Foo** result)みたいな感じで)。

PInvokeGeneratorはこれらをIntPtrにマッピングします:

internal static extern int soundio_outstream_begin_write(
    IntPtr outstream, IntPtr areas, IntPtr frame_count);

こんなAPIで大丈夫か? ちゃんと使えるのでしょうか? 使っている部分を見てみましょう:

public WriteResults BeginWrite (ref int frameCount)
{
    IntPtr ptrs = default (IntPtr);
    int nativeFrameCount = frameCount;
    unsafe {
        var frameCountPtr = &nativeFrameCount;
        var ptrptr = &ptrs;
        var ret = (SoundIoError)Natives.soundio_outstream_begin_write (
            handle, (IntPtr) ptrptr, (IntPtr) frameCountPtr);
        frameCount = *frameCountPtr;
        if (ret != SoundIoError.SoundIoErrorNone)
            throw new SoundIOException (ret);
        return new WriteResults (ptrs, Layout.ChannelCount, frameCount);
    }
}

soundio_outstream_begin_write()の呼び出しで渡されている2番目の引数には、IntPtr型の変数ptrsのアドレス&ptrsを保持しているptrptrが渡されています。ptrptrの型はIntPtr*です。2番目の引数はIntPtrなので、IntPtrにキャストします。3番目の引数には、frameCountを格納したnativeFrameCountというint変数へのアドレスframeCountPtrを渡しています。frameCountPtrの型はint*なので、これもメソッド定義に合うようIntPtrにキャストします。 &* などの演算子を使えれば何とかなりそうです。

呼び出し後は、frameCountPtrの値をポインタの参照先から取り出します(*frameCountPtr)。ptrptrのほうはもう少しややこしいのですが、これはパフォーマンスを考慮してWriteResultsという独自の構造体で処理しています。この構造体の中ではSoundIOChannelAreaという構造体を取得するGetArea(int channel)というメソッドがあり、これは、(non-publicな)SoundIoChannelArea構造体へのポインタのみを含む、(publicな)SoundIOChannelArea構造体を生成して返します。当初はクラスだったのですが、頻繁に生成して返すことがわかった時点で構造体にしました。

もちろん、C#でrefやoutを使えば、こんなことをする必要はありません。しかしP/Invokeメソッドを全て定期的に自動生成したい場合は、この手法が便利です。自動生成ツールは、どの関数のどの引数がref/outを使うのか判断することが出来ません。swigみたいにマッピング定義ファイルに基づいて行うのも良いでしょうが、それでは手軽さが失われてしまいます。とりあえず今回はマッピング加工無しでどこまでいけるかという実験としました。

構造体へのポインタからメンバーの値を取り出す

P/Invokeと切っても切り離せないMarshalクラスには、ポインタと文字列や構造体の間でマッピングやらマネージド型の生成やらを行うメソッドが数多く含まれています。Marshal.PtrToStructure<T>()Marshal.PtrToStringAnsi()などが典型的な例でしょう。

Marshalクラス、P/Invokeに必要そうな機能はたいてい揃っているように見えるのですが、構造体へのポインタからその構造体メンバーの値を設定するのは容易ではありません:

// C
struct SoundIoChannelArea {
    void *ptr;
    int count;
}

// C#
struct SoundIoChannelArea {
    public IntPtr ptr;
    public int count;
}

struct SoundIOChannelArea {
    IntPtr handle; // SoundIoChannelArea*
}

SoundIOChannelAreaオブジェクトに、Cコードのcountをget/setするCountプロパティを定義するにはどうすればよいでしょうか?

getterは実のところ簡単です:

get { return Marshal.PtrToStructure<SoundIoChannelArea> (handle).count; }

PtrToStructure<T>()でポインタから構造体に変換するだけです。(ただし、(後述しますが)このメソッドの利用はお勧めしません。)

setterはそうはいきません。PtrToStructure<T>()で取得したメモリは、マネージドコードで処理しても呼び出し元に影響を与えないコピーです。コピーの値を変更してもオリジナルには反映されないので意味がないですね。こういう場合には、Marshal.WriteXxx()を使います:

public IntPtr Pointer {
    get { return Marshal.ReadIntPtr (handle, ptr_offset); }
    set { Marshal.WriteIntPtr (handle, ptr_offset, value); }
}
static readonly int ptr_offset = (int) Marshal.OffsetOf<SoundIoChannelArea> ("ptr");

Marshal.OffsetOf<T>(handle)というメソッドで、C#構造体のメンバーのポインタからのオフセットを取得できます。WriteIntPtr()ではこれを利用します。

構造体に含まれる構造体へのポインタを取得する

応用問題として、構造体の中に構造体が(ポインタではなくそのまま)入っている場合があります:

struct SoundIoOutStream // soundio.h (497, 8)
{
    public IntPtr device;
    public SoundIoFormat format;
    public int sample_rate;
    public SoundIoChannelLayout layout;
    ...
}

SoundIoOutStreamをラップするSoundIOOutStreamクラスから、layoutフィールドの値…としてSoundIoChannelLayoutをラップするSoundIOChannelLayoutを返す方法は、自明ではありません。SoundIOChannelLayoutを返すには、SoundIoChannelLayoutのポインタが必要になりますが、Marshal.PtrToStructure<T>()SoundIoOutStreamの構造体のコピーを取得してしまうと、SoundIoChannelLayoutは(いくらオフセットを持っていても)このコピーの中のメモリにしかアクセスできないので意味がありません。

PtrToStructure<T>()がさらにいただけないのは、コレ、必ず引数をboxingするんですよね。boxing不要なメソッドシグネチャの意味とは…(ひとつ考えられる理由としては、P/Invokeではジェネリクスが使えないのでこうなってしまったのだろうと思います。internal callでも制約は同じなのでしょう。)

ともあれ、こういうときは、親はポインタのまま、オブジェクトのハンドルにメンバーのoffsetを加算すれば、子もポインタのまま取得できます:

public SoundIOChannelLayout Layout {
    get { return new SoundIOChannelLayout (handle, layout_offset); }
}
static readonly int layout_offset = (int) Marshal.OffsetOf<SoundIoOutStream> ("layout");

構造体内部にある構造体を値として外部から設定する

上記の類似問題として、今度は値を取得ではなく設定する場合はどうでしょうか。

public SoundIOChannelLayout Layout {
    set { Marshal.WriteOMGWHATDOINEEDHERE<SoundIoOutStream> (handle,  layout_offset, valueOMGWHATSHOULDIDOHERE); }
}
static readonly int layout_offset = (int) Marshal.OffsetOf<SoundIoOutStream> ("layout");

任意の構造体を読み書きできるようなメソッドはMarshalクラスには無いんですね。こういう時はBuffer.MemoryCopy()を使うとよいでしょう:

unsafe {
    Buffer.MemoryCopy ((void*)((IntPtr)handle + layout_offset), (void*)value.Handle,
               Marshal.SizeOf<SoundIoChannelLayout> (), Marshal.SizeOf<SoundIoChannelLayout> ());
}

corefxではBuffer.MemoryCopy()みたいな古いAPIでも今年になって最適化が図られていたりするので、monoもこっちベースにならんかなあとか思うわけですが、とりあえず今のところはreferencesourceベースのようです。まあでもそれなりの速度は出るでしょう。

あとcorefxにはSystem.Runtime.CompilerServices.Unsafeというクラスが追加されていて、ReadWriteReadUnalignedWriteUnalignedなど便利そうなやつを持っているので、これを使うという手もありそうです。ilasmのソースで実装されていてP/Invokeやinternal callはしていなさそうなので、多分monoでも使えるのではないかと思いますが(古いUnityとか無理そう)、今回は依存しないことにしました。

C#配列にIntPtrからデータをコピーする

SoundIOInStreamのread callbackから取得したデータはIntPtrでチャネルごとに格納されてきますが、これをたとえばSystem.IO APIを使ってファイルに保存したい場合は、byte配列などに格納してやらなければならなくなります。これはちょっと面倒ですね。

Marshal.UnsafeAddressOfPinnedArrayElement()を使ってポインタにしてしまえば、Buffer.MemoryCopy()が使えるのですが、いったんpinned objectにしないといけないはずなので、このメソッドにそのまま渡すだけではダメでしょう。こういうときは単に配列にfixedを使うとよいでしょう(使えます):

var arr = new byte [capacity];
unsafe {
    fixed (void* arrptr = arr) {
        int fill_bytes = ring_buffer.FillCount;
        IntPtr read_buf = ring_buffer.ReadPointer;
        Buffer.MemoryCopy ((void*)read_buf, arrptr, fill_bytes, fill_bytes);

その他のP/Invokeトラブルシューティング

  • memset()でゼロ化する方法が無い。
    • これはmonoランタイム限定の裏技ですが、kernel32 APIのCopyMemory, FillMemory, MoveMemory, ZeroMemoryに限って言えば、kernel32.dllを(擬似的に)DllImportすることで、これらに相当するmonoランタイム内にある代替実装が機能するかもしれません。mono/data/config.inを参照。あるいはdll.configではplatformによる条件指定や名前の再マッピングもできるようになっているので、必要だったらそれらを駆使すればいけます(と断言していますが未検証)。
    • Unsafeの中にInitBlock()なるメソッドがあるのを発見しました。CILのinitblock命令を呼び出すだけに近い、かなりシンプルな内容ですが、目的はこれで果たせそうです。

monoと.NET Coreの同時サポート

今回のlibsoundio-sharpは、.NET Coreプロジェクトとしてもビルドできます。実体は極めてシンプルで、ライブラリ用の.csprojとNUnitテスト用の.csprojの2ファイルが存在するだけです。どちらも、"SDK"を指定する新しい方式の.csprojで、ソースファイルは(サブディレクトリには無いので自動化はできず)ワイルドカードで指定しているだけです。これだけなら5分もあれば.NET Core対応できますね。サンプルは対応していませんが…まあ5分もあればできるんじゃないですか? 誰にでも。

テストには NUnit3TestAdapter を使っています。dotnet test のみでビルド・実行できるようになっています。最初はxunitで独自にテストを書いてdotnet xunitで実行していたのですが、NUnitと両方メンテするのはバカバカしいのですぐに止めました。いったん2つの*.csprojを作成した後は、無変更でテストできています。

ちなみに「monoと…」と書いていますが、.NET Framework + Windowsでも当然使えるはずです(動作確認はしていないけど、.NET Coreでは動いているし…)。

System.IntPtrPointer<T>の使い分け

PInvokeGeneratorは、JNAやBridJなどでもよく使われる「ポインタを表現する型」としてPointer<T>という型を追加するのですが(ちなみに全部生成します。「ランタイム」を作りたくないので)、これはDllImportされるメソッドの定義などでは使われていません。.NET (Framework / Core) のP/Invokeジェネリック型を受け付けないんですね。monoランタイムの環境だと通ったりするので、.NET Coreで動かしてみて初めて気づいたりしました。

実用上はimplicit演算子の実装を追加しているので、あまり気にする場面は無いと思います。

Pointer<T>みたいな基本的な型を定義してP/Invokeで使えない、というのは、C#の重大な欠点のひとつだと思います。Kotlin/Nativeのcinteropを見てみましょうCPointer<T>CValuesRef<T>CValue<T>など、ジェネリクスが過剰な制限に足を引っ張られること無くふんだんに活用されています。

C# 7.2は値型をある程度柔軟に扱えるようになりましたが、この辺りにはまだまだ他の言語を見習って改善する余地がありますね。

次の課題

PInvokeGeneratorでC#のコードは自動生成できて楽になった(?)のですが、サウンド関連のコードは前述のvst3sdkも含め、けっこうC++のものが多いんですよね…というわけで、今度はC++APIから一旦CのAPIを自動生成して、それをP/Invokeできるような仕組みを考えています。CppSharpで出来そうな領域ですが、CppSharpがなかなか安定してLinux上でビルドできない/動作しないので自分で作りそうです(nclangともだいぶ役割がかぶっている)。まあ何かしら成果が出たら公開すると思います。

以上で7日目の分はおしまいです。明日は増田さん @moonmile のターンです(なにこのXamarinシーケンス)。よろしくお願いします。

Extensive Xamarin @ 技術書典3

こちらではだいぶギリギリの告知となってしまいましたが、10/22(日)の技術書典3で、Xamarin周辺コミュニティの何人かの方と一緒に、Xamaritansの名前で(これいつまで使うんだろ)同人誌の新刊を出します。

techbookfest.org

サークル配置番号は「お-13」、新刊は1000円で頒布する予定です。当日頒布分については、電子版のダウンロードカードもお渡しする予定です。(問題なく作業する余裕があれば…)

書名は"Extensive Xamarin"としました。書籍紹介に、まえがきのために書いたポエムをコピペってあるのですが、前作"Essential Xamarin"とはだいぶ方向性が変わって、実践的な各論を中心として、Xamarinの基本を押さえた「その先にあるもの」がたくさん集まっている1冊になっています。記事の難易度も、入門的でわかりやすいものから難解で読み応えのあるものまで、幅広く集まったと思います。

内容の詳細、プレビューが、新刊情報の公式ページで公開されているので、そちらも見てやってください。

当日は既刊"Essential Xamarin"の同人誌版も販売する予定です。

前回"Essential Xamarin"を新刊として出した技術書典2の頃は、Xamarinの新鮮な情報がほぼ書籍として存在していない状況で、各メンバーが本気で最新情報を大量に盛り込んで来た結果、印刷の都合もあって2冊に分けるほどになり、商業化されて達人出版会などでも販売されるようになりましたが、Xamarin本はここ半年くらいで基本書の類がかなり充実してきました。なので、技術書典3では商業化を目標とするのではなく、もう少し本来の「同人誌らしい」「ポピュラーなニーズを気にせず」「書きたいことを書ける」ものにしたいなあという気持ちがそこそこありました。執筆メンバーが締切を前にのびのびと書けたかはわかりませんが(!?)、結果的に集まってきた原稿を見ていて、概ね目的は達成できたかなと思っています。

ちなみに今回は裏表紙までイラストを描いていただいていたりします。贅沢仕様! (この元ネタが分かる人はほとんどいないと思いますが…)

f:id:atsushieno:20171019120618p:plain:w300:h250

10/22当日ですが、わたしは技術書典本体のスタッフとして行動しているので、残念ながら来てくださる方とお話しする余裕は多分無いんじゃないかと思いますが、サークルのブースでは他の執筆メンバーにお手伝いしていただくことになっています。今回は4Fに休憩スペースも設けてあり、整理券でいつでも再入場可能なので、ひと通り見終わったらそちらに移動して戦利品の見せ合いなどしてもらえればと思います。

今回は技術書典3本体の準備も割と忙しく、周りの何人かの方にお手伝いをお願いしたり、自分のサークル側の作業もだいぶ滞ったりと、なかなかタフな状況なのですが、当日まで何とかやっていきたいと思いますんで、みなさん技術書典3にぜひ遊びに来てやってください。

iOSDC2017に参加してきた

オマエiOSやらないじゃん?というかそもそもモバイルアプリケーションを開発するやつじゃないじゃん?という感じだけど、iOS開発ツールやObjC/Swiftについては調べたり書いたりすることもあるし(今は技術書典3向けの原稿に書くためにいろいろ調べるフェーズ)、AndroidコミュニティはけっこうわかってきたけどiOSコミュニティはほとんど知らないので、飛び込んでいろいろ教えを請うべきだろうと思って参加してみた。

 

iosdc.jp

Ubuntu使いのAndroid使いのアプリケーション開発しないわたしはあらゆる意味でアウェイだったのだけど(あ、それ.NETクラスタでもそうですかね…)、面白いセッションがたくさんあったしLTもうまいのがたくさんあったし、次回もぜひとも参加したいと思えるいいカンファレンスだった。個人的にはなぜか台湾から来ていたグループとランチしていたり(わたしがセッションやったMOPCON2014のTシャツで来ているのがいて声をかけずにはいられなかった。あと残りはKKBoxの面々だったけど、昔なぜか台北に住んでいた時にCEOのChrisの接待トークをやらされたことが…)、懇親会で中国人クラスタと話し込んだりしていて、あんまりiOS感があったわけでもなかったけど、それはそれでいいんじゃなかろうか。何の業だろうな。

 

今回はいろいろ調べて面白かったAutoLayoutの話を書きたい。1日目のキーノート的なセッションだ。

speakerdeck.com

AutoLayoutを実装しているレイアウトエンジンのアルゴリズムについての解説で、内容はAutoLayoutの制約ベースのレイアウトを実現しているCassowaryというアルゴリズムの説明だった。前段から数式だらけだった(そのうちtwitterのiOSDCまとめとか出来るんだろうと思うけど、「あーそういうことね完全に理解した」(画像略)の流れである)のだけど、それは制約ベースのレイアウトというのが本質的に線形計画問題となるからだ。ということだ。

詳しくはセッション資料にわかりやすくまとめられているけど、AutoLayoutは、複数の「制約」を(優先度を考慮しつつ)最大限満たすレイアウトを自動計算するもので、これは線形計画問題として捉えることができる。線形計画法は日本の最初のソフトウェア特許訴訟(カーマーカー特許)というかたちで知財クラスタにはよく知られている。コンピューターの計算量を最大限に活用して数学の問題を解決する技術だ。今回のセッションでは「複雑な計算をしている(だから計算力のあるマシンが求められる)」という反応がよく見られたが、線形計画問題を高い計算力で解決するというのは伝統的なソフトウェア技術の応用だ。

Cassowaryアルゴリズムというのは、レイアウト問題を線形計画問題の応用として解決する解法と考えれば良さそうだ。そして、レイアウトの問題は特定のGUIシステムに固有のものではないので、さまざまな言語・環境向けのCassowaryの実装が存在しているようだ。AndroidにもConstraintLayoutがあるけど、これもCassowaryの実装だそうだ。

www.bignerdranch.com

もっとびっくりしたのは、Gtkにすら存在していて、それがDiaCanvasが使われていた頃から存在していた、ということだ。2001年なんてもしかしてみんな生まれていないんじゃないの…

sourceforge.net

ちなみにGtk向けにはもうちょっとモダンなやつがあるようだ。

github.com

以前から「AutoLayoutもConstraintLayoutもなんか似ているっぽいしクロスプラットフォームでそれっぽいものを実現できるんじゃないの」と思っていたのだけど、どちらもアイディアが同じアルゴリズムで(先にリンクしたページにあるように)実装の違いはあっても(VisualFormatはAutoLayoutだけのDSLだろう)、レイアウトエンジンが共通化できるというのは夢のある話だ。たとえば、Cで共通コードを書いておいて、それをXamarin.Formsにダイレクトに適用できるということだ。

twitterでは何度か言及しているのだけど、XamarinではCSSのFlexBoxのモデルをCで実装したコードが開発されている。コミットログを見れば分かるけどRubyMotionハカー(lrz)がうちに入ってきてから主に作っていたプロジェクトだ。

github.comFacebookにもyogaというプロジェクトがあって、そっちのほうが有名だと思うけど、Cでレイアウトエンジンを実装しておいて、それを全方面で活用するというやり方は、今後もいくつか出てくるかもしれない。Cassowaryも完全に同じ流れでいけるような気がする。P/Invokeで呼び出すプラットフォーム中立なライブラリを作ってから、それをFormsのレイアウトシステムに合わせるのが楽そうだ。

…とまあ、こういう思索がいろいろ出来た刺激的なセッションだった。わたしはGUIより他にやりたいことがありすぎてこっちまでやる時間は無いと思うので、GUIを作り込むのが好きな人がいたらおひとつどうですかね?

 

追記: 更にいろいろあることを教えていただいた:

 QtもAngularもある…! すごい。

D8コンパイラをXamarin.Android(d15-5以降)で使う

今からだいたい1ヶ月前に、Googleが新しいDexコンパイラd8を紹介していました。

android-developers.googleblog.com

d8Android SDKの既存のツールで言えばdxに相当する機能を実装しています。dxはJava bytecode (*.class)をDalvik bytecode (*.dex)に変換するコンパイラですね。通常はclasses.dexをビルドするために使われています(が、大抵の人は意識していないでしょう)。

Android Studio 3.0のbetaでは既にこのd8を有効に出来ます。既に使えるならXamarin.Androidでも使えるようにしたいところです。ただ、リリース当初はd8のソースが見当たらず、しばらく待っていました。その後、d8がr8のリポジトリに含まれていると教えてもらったので、試しに使ってみたら上手くいきました。

というわけで、dxの代わりにr8を使うようなNuGetパッケージを作っておきました。ソース…と言うほどのものは無いのですが…はここにあります。ちなみにxamarin-androidに加えた変更もかなり小さいものです。

github.com

xamarin-android-d8-build をpackages.config に追加するとインストールできます(このパッケージはまだリスティングに載せていないので、パッケージマネージャのUIで検索しても出てきません)。パッケージをインストールするだけでMSBuildプロパティが追加されるだけで、Xamarin.Androidアプリケーション以外で副作用は無いはずです。

このパッケージですが、実のところXamarin.Androidを製品版から使用している人には使えません。xamarin-androidをmasterからビルドしている人は今すぐ使えます。そうでない人はd15-5ブランチがalpha channelに降ってきてから利用できるようになります。ちなみに今はd15-3がstable、d15-4がalphaですね。

これ自体は大したコードの変更を伴っていないのに、なぜこんな根本的な差し替えが可能なのでしょうか? それは、Xamarin.AndroidMSBuildタスクを読み解くことが出来れば誰にでも出来ます(MSBuildタスクはデバッグビルドのサポート固有の部分以外はgithubで公開されています)。dx.jarを呼び出す部分はCompileToDalvikターゲットであり、CompileToDalvikタスクでコマンドラインを生成しています。ここではdx.jarはDxJarPathプロパティ、その引数はDxExtraArgumentsプロパティで指定されています。

今回のNuGetパッケージはこれらのプロパティをオーバーライドして、同梱のr8.jarを使用するように指定しているだけです。ちなみにdx.jarを使う場合は--dex--no-strictというオプションが必要なので、これらはDxExtraArgumentsのデフォルト値となっています。r8を既存のCompileToDalvikタスクで使うにはこれらが邪魔だったので、消す作業をした、というわけです。

DxJarPathはもともとカスタマイズ可能になっていたので、全体的な変更も些少なものです。

…というわけで、ごく簡単なハックですが、MSBuildタスクをどうやってカスタマイズすればいいか、簡単に解説できるネタだったのでまとめてみました。