MSBuildのバッチ機能とは
みなさん MSBuild プロジェクトの手書き、していますか?プロジェクトの手書きはさまざま苦労はあると思いますが、そんなご時世に、知っているとちょっと楽に書けて、しかもインクリメンタルビルドがしやすくなるバッチ機能について、詳しく見ていきましょう。
バッチの概要と使い方については、公式ドキュメントに書いてありますが、腑に落ちないというか、あまり理解できた感じがしなかったので、 MSBuild のソースコードとステップ実行してみた結果を基に、具体的な動作から挙動を説明していきます。
- 用語について
- 基本的なバッチの動作
- タスク
- ItemGroup
- PropertyGroup
- ターゲット
- ターゲットのバッチ実行の基本
- 悪用
- プロパティや項目を参照するときの注意
- バッチ実行計画を作成するプログラム
- PrepareBatchingBuckets の入出力
- 項目分割アルゴリズム
- 終わりに
はじめての仮想HID
半年ぶりです。崇高な計画を遂行するために、仮想 HID が欲しくなったので、ドライバーから作ってみようと思い、いじってみた記録を書いておきます。
HID ドライバーのサンプル
Microsoft 公式のドキュメントで HID ドライバーのサンプルが公開されています。
これをパクれば簡単ですね!
……ミニマルサンプルにしてはでかすぎて理解できない。
というわけで、これをすべて理解する前に、もっと簡単そうな方法を見つけたので、それを試してみました。
Virtual HID Framework
Windows 10 から Virtual HID Framework(略して VHF)が登場しました。今までの HID ドライバーでは、 Windows が用意してくれるのは「これは HID ですよ」と宣言してくれる機能くらいで、多数の I/O リクエストがパススルーされてくるので、それに対応する処理をすべて書く必要がありました。対して VHF を使用したドライバーは、それ自身が HID として振舞うのではなく、 HID の作成、削除、 I/O 処理を管理するドライバーとして振舞います。このことから、ドキュメントでは「HID source driver」と呼ばれています。
VHF を使った HID ソースドライバーの作り方の公式ドキュメントはここにあります。
これを読めば満足という方は、このブログを閉じても大丈夫です。
ドキュメントの「Virtual HID device tree」の図を見てもらうと、 HID ソースの働きがわかると思いますが、 HID ソースは、自身が 0 個以上の子 HID を持つデバイスとして振舞います。 Windows のドライバー業界でバスと呼ばれているやつです。子 HID には Windows に組み込まれている VHF 用の仮想 HID ドライバーが使用されます。
デバイスマネージャーの「接続別」で表示するとこんな感じになっています。
HID ソース → 仮想 HID と接続され、 HID の種類からキーボードであることを認識してキーボードのドライバが読み込まれています。
MPMアルゴリズムのNSDFの求めかた
結論だけ知りたい人向け
NSDF の式
の について、前から求めると
後ろから求めると
となるので、好きな方でやってください。
はじめに
ピッチ検出アルゴリズムについて検索すると、まぁ大体このページ(音声の波形からピッチを検出するアルゴリズム - まめめも)に行き着くと思うのですが、 NSDF のインクリメンタルな求め方について特に説明もなく、クワイン用の well compressed なプログラムしか書いていないので、基の論文であるところの『A Smarter Way to Find Pitch』に書いてある式を変形しながら、本当にインクリメンタルに求まるのか検証していきます。
ここでは Normalized Square Difference Function の略として NSDF と言っていますが、 McLeod 氏の 2008 年の論文では specially-normalized autocorrelation function で略して SNAC と呼んでいますね。まぁいいや。
さぁ fixed を捨てて Unsafe だ
早すぎる最適化が好きな人のための C# 7 の有効活用ガイドです。
ある構造体をそのまま byte
配列に突っ込みたくなるとき、ありますよね?構造体ならメンバーに名前がついていて書きやすい、でも相手が byte
配列だから 1 バイトずつ手書きするしかないのか……?そんなときにおすすめの技を紹介します。
達成目標
例を用意しましょう。 X Window System のプロトコルは C 言語などでクライアントを実装しやすいように、適度にアライメントされたデータをやりとりします。しかもエンディアンもクライアント側が指定することができるので、クライアントはまさに構造体を直接送受信することができます。そこで、クライアントが X サーバに接続して、最初に送信するメッセージを構造体を使って中身を用意し、 byte
配列に書き込むことを目標にしていきましょう。
(この目標設定は、ちょうど X クライアントを C# で書いていたからなのですが、完成したころに公開します。公開しないかもしれません。)
公開してあります → X11Client.cs
それでも僕はWineでMonoを動かしたかった
Wine 上で動く Windows アプリを制御するにはどうしよう?と考えた結果、制御する側も Wine 上で動かせば Windows 上と同じように操作できるじゃないか!と思いやっていった記録。残念ながら、結局 Docker 上で動かすことはできなかったので、僕の意志を引き継いでくれる方、もしくは先行研究がありましたら教えてください。
というわけで、 Docker で動かすのを断念するまでに得た知見をまとめておきたいと思います。
wine-mono?それともスタンドアロンの Mono?
wine-mono は Mono のコアライブラリ + .NET Framework になりすますための mscoree.dll という構成になっています。つまり .NET の exe ファイルを起動しようとすると Mono を使ってくれる、という構成になります。
それに対して、 mono-project.com からダウンロードできる Mono の Windows 版は mono.exe を使ってアセンブリを実行する形をとります。つまり Linux で使っている Mono とまったく同じです。
これらふたつは共存することができるので、間接的に .NET Framework を必要とするアプリには wine-mono でうまくやっておいてもらいつつ、 .NET アセンブリだと最初からわかっているものは mono.exe で起動するという使い方ができるでしょう。
さて、最初は wine-mono で全部なんとかしようと思っていたのですが、厄介な問題にぶち当たりました。 wine-mono はアセンブリの厳密名を検証するようですが、回避するために App.config に <bindingRedirect>
を書いても読み込んでくれないようです。というわけで、基本的には mono.exe を使うべきだということがわかりました。
ちゃんとログに config 読み込めてないの書いてあったわ… pic.twitter.com/c1Lbo6zRcc
— 天はツイの上で敵を作らず (@azyobuzin) 2017年8月9日
つまりですよ、 Mono が mscoree として振舞うから面倒なのであって、 Windows 版の Mono を普通にインストールすればいいのでは?
— 天はツイの上で敵を作らず (@azyobuzin) 2017年8月9日
I/O待ちのためのTaskとバックグラウンド処理のためのTask
ポエポエ~~
I/O 完了待ちを行う OS 機能の抽象化としての Task とスレッドプールに積んだ処理の抽象化としての Task をごっちゃにしてて反省
— 優勝したい (@azyobuzin) 2017年6月2日
の一連のツイートのまとめでもしておこうかと。
I/O 待ちのための Task
I/O 処理を OS に投げて、完了を待機するのを抽象化した Task を「I/O 待ちのための Task」と呼んでいきます。身近かつ純粋な I/O 待ちを行う Task の例として、 FileStream を FileOptions.Asynchronous
オプションをつけて開いたときの ReadAsync が挙げられます。
例として、 ReadAsync を await したときの挙動を見てみましょう。
async Task FileReadSample() { const int bufSize = 10; // FileOptions を指定するために省略できないオプション大杉 using (var fs = new FileStream( "test.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufSize, FileOptions.Asynchronous)) { var buf = new byte[bufSize]; var readBytes = await fs.ReadAsync(buf, 0, bufSize); Console.WriteLine(readBytes); } }
「FileReadSample 続き」がどこで実行されるかは、このメソッドがどんな SynchronizationContext で呼び出されたかによりますが、とにかく OS からの非同期処理完了をスレッドプールが管理している I/O 完了ポートで待ち受け、 Task の完了を通知して、継続タスクを実行するという動きをします。
このとき ReadAsync 単体で見ると、 I/O 完了ポート用のスレッドが足りていれば、新しいスレッドを作成することも、スレッドプールのキューに新たにタスクを追加することもありません。つまり「I/O 待ちのための Task」は OS の非同期処理 API をラップし、継続タスクへの通知を行うだけのものといえます。
ただし、 await は SynchronizationContext が特別な場合(UIスレッドなど)を除き、続きをスレッドプールのキューに追加します。これはコールスタックが大きくなるのを防ぐためらしいですが、それが嫌なら ContinueWith で TaskContinuationOptions.ExecuteSynchronously
を指定することで、完了通知を受け取ったスレッド上でそのまま続きが実行されます(本当?)。
より詳しく: Async訪ねて3000里
スレッドプールの有効利用
I/O 待ちのための Task は、スレッドプールを有効的に利用するために使います。
スレッドプールは短時間で終わる処理が大量にあるときに効率良くスレッドを利用する手段ですから、 I/O 待ちでスレッドプールのスレッドをブロックしてしまうのはスレッドプールの効率を落としてしまいます。そこで I/O を非同期で行い、完了を待っている間にキューに溜まった他の処理を実行できるように、I/O 待ちのための Task を利用します。
逆に、スレッドプール内で動いている Task でバックグラウンド処理のための Task を await するのは得策ではありません。なぜなら、そのまま続行できる処理をわざわざスレッドプールのキューに入れることになり、切り替えコストが発生するからです。
スレッドプールを有効利用することが求められるのは、web アプリケーションとライブラリです。
まず、 web アプリケーションでは、リクエストごとの処理がスレッドプールのキューに追加されます。ここでスレッドをブロックするようなコードを書いてしまうと、十分な性能を出すために多くのスレッドが必要となってしまいます。スレッドプールに割り当てるスレッドが多くなりすぎると、メモリ使用量がどんどん増えていってしまいますし、スレッドの管理コストも上がってしまいます。このような理由から、I/O 待ちのための Task を利用すると必要なスレッド数を減らせて、リソースを削減できます。
次にライブラリですが、ライブラリはあらゆる .NET アプリケーションから使われるので、 web アプリケーションから使われることを考えると同じことが言えます。
バックグラウンド処理のための Task
UI スレッドのフリーズを防ぐため、または並列処理のために処理を分割したものを「バックグラウンド処理のための Task」と呼んでいきます。バックグラウンド処理のための Task は、 CPU で大量の計算を行うときに Task.Run
などを使って作成します。
「~Async」メソッドの罠
「~Async」という名前がついているメソッドは、一般的なライブラリの場合 I/O 待ちのための Task を表していることが多いです。理由は「スレッドプールの有効活用」で示した通りです。
I/O 待ちのための Task を返すメソッドが実際に非同期で処理するのは I/O だけです。だから、 I/O が発生する前に重い計算をしていても、それはメソッドを呼び出したスレッドで実行されているかもしれません。もし、 GUI アプリケーションでそのようなメソッドを UI をフリーズさせずに実行したいならば、
var taskResult = await Task.Run(async () => { var fooResult = await FooAsync(); // 後処理 }); // taskResult を UI に反映したり
といったように、バックグラウンド処理のための Task に変換する必要があります。
また、 Task を返すメソッドだからといって、必ず非同期処理が行われるわけではありません。例えば、条件によって I/O が発生しないメソッドは、条件を満たすときに完了済みタスクを返します。 await は Awaiter の IsCompleted が true のとき、すなわち完了済みタスクが返ってきたときには、現在のスレッドのまま処理を続行します。このことを覚えておかないと、「メソッド内の1回目の await で ConfigureAwait(false)
を指定したから、2回目からは要らないよね」とか言って死にます。
TargetFrameworksからビルドできそうなフレームワークだけビルドする
これから1ヶ月以上ブログ書いてなかった。というわけで、これの続き。
この記事では PCL や Xamarin 用のアセンブリを dotnet build でビルドできるようにしましたが、これだと MS の不自由アセンブリや Xamarin がインストールされていないとエラーになってしまうので、なんとかして警告レベルに落としたいので、やっていきます。
まずは対処前の状況。こんな感じのプロジェクトをビルドしていきます。
<TargetFrameworks>netstandard1.1;netstandard1.5;portable45-net45+win8+wpa81;monoandroid10;monotouch10;xamarinios10</TargetFrameworks>
するとこう。
PCL がコケたところでビルドが止まってしまっていますね。というか並列ビルドしてくれよ。
では、 TargetFrameworks
からビルドできなさそうなフレームワークを消していくコードを追加していきましょう。やり方を言葉で表すとこんな感じ。
- まとめてビルドするやつ(
DispatchToInnerBuilds
ターゲット)の実行前にフック - 次の手順をすべてのフレームワークに実行(といっても netstandard や netcoreapp は絶対ビルドできるし、次の手順には失敗するので素通りさせる)
GetReferenceAssemblyPaths
タスクを叩いてみる- 結果が空でないなら
TargetFrameworks
に追加する
以上を全フレームワークに実行します。
というわけで実際のコードはこちら。
<Target Name="CheckAvailableFrameworks" BeforeTargets="DispatchToInnerBuilds"> <ItemGroup> <!-- _TargetFramework という名前は DispatchToInnerBuilds 内で使われているので、被らないようにアンダーバー増やした --> <__TargetFramework Include="$(TargetFrameworks)" /> <!-- netcoreapp もあるならそれもちゃんと Condition に書きましょう --> <_OldTargetFramework Include="@(__TargetFramework)" Condition="!$([System.String]::Copy('%(Identity)').StartsWith('netstandard'))" /> <_NewTargetFramework Include="@(__TargetFramework)" Condition="$([System.String]::Copy('%(Identity)').StartsWith('netstandard'))" /> </ItemGroup> <!-- TargetFramework プロパティをセットして CheckCompilability を呼び出す --> <!-- TargetFramework プロパティがセットされていると、うまいこと TargetFrameworkMoniker プロパティがセットされるのを利用していきたい --> <MSBuild Projects="$(MSBuildProjectFile)" Targets="CheckCompilability" Properties="TargetFramework=%(_OldTargetFramework.Identity)"> <!-- 結果をとりあえずアイテム化しておいて --> <Output TaskParameter="TargetOutputs" ItemName="_NewTargetFramework" /> </MSBuild> <PropertyGroup> <!-- 強引にプロパティに変換する --> <TargetFrameworks>@(_NewTargetFramework)</TargetFrameworks> </PropertyGroup> </Target> <Target Name="CheckCompilability" Returns="$(CompilableFramework)"> <!-- ContinueOnError="true" によってエラーを警告化 --> <!-- これで完全勝利 --> <GetReferenceAssemblyPaths TargetFrameworkMoniker="$(TargetFrameworkMoniker)" RootPath="$(TargetFrameworkRootPath)" ContinueOnError="true"> <Output TaskParameter="ReferenceAssemblyPaths" PropertyName="_ReferenceAssemblyPaths" /> </GetReferenceAssemblyPaths> <PropertyGroup Condition="'$(_ReferenceAssemblyPaths)' != ''"> <!-- 成功したときだけ Returns に追加されるってわけ --> <CompilableFramework>$(TargetFramework)</CompilableFramework> </PropertyGroup> </Target>
これで警告になりました。めでたしめでたし。