WSL (Bash on Windows) で Windows のプログラムが実行可能になったので遊んでみた

本当は、先週ぐらいに

Announcing Windows 10 Insider Preview Build 14951 for Mobile and PC | Windows Experience Blog

があって、この Build 14951 で、Windows側のプログラム呼べるようになったよんって書いてあったんだけど、待てど暮らせど、アップデートが落ちてこず。結局、

blogs.windows.com

これが落ちてくるまで待つことになりました。

パイプが動く

さて、この仕組みで、実行できるだけなら普通なんだけど、ちゃんとパイプが動く。
なので、Windows 側の dir の結果を Ubuntu 側で grep したりできる。

f:id:espresso3389:20161027193928p:plain

まぁ、Windows の dir は内部コマンドなので、 cmd.exe を経由しないといけませんが、そうすればいいだけです。
素敵です。逆向きも当然。

GUIアプリを起動してみる

notepad.exe を起動するとどうなるでしょうか?

f:id:espresso3389:20161027192049p:plain

おや、 cmd.exe と違って、ターミナルに制御が戻ってきません(プロンプトが出てない)。
つまり、標準出力・入力系は繋がりっぱなしですね。これ。

そして、この状態で、Ctrl-C を押すと、ターミナルに制御が戻ってきます(プロンプトが表示される)。
ただし、 notepad.exe は死にません。起動しっぱなしです。

どういうこと?っていう感じで、もう一度、 notepad.exe を起動して、 ps でプロセスを見てみると、

f:id:espresso3389:20161027193853p:plain

Windows のプロセスは、 /init 経由で呼ばれてるんですね。
で、Ctrl-C で、 SIGINT を送り付けたら、 /init だけが死ぬと。
init が死ぬって結構、致命的に聞こえますが、まぁ、この init はそういうやつじゃないと。

で、 notepad.exe は死にませぬ。
なので、自分で起動したプログラムの面倒を自分で見切れないという意味では辛いかもしれません。

で、なんで Windows のプログラムが動くの?

スペシャルバージョンのカーネルが動いてます!・・・っていうわけじゃないです。
そもそも、この辺のバイナリは、普通の Ubuntu のものと同一ですしね。

何が起きているかというと、実は、スゲー昔に書いたことあるんですが、

espresso3389.hatenablog.com

の中に、追記で、 binfmt_misc って奴の話があります。これは、Owin の話ですね。
要は、Linux カーネルには、実行ファイルの先頭のバイト列を見て、そのファイルを解釈するプログラムを起動する仕組みがあるんですね。シェルスクリプトの #!/bin/sh とかの拡張だと思えばわかりやすい。
mono とか、 Wine はこの仕組みを利用して、 Linux 上で、 exe が直接動くような感じを実現しています。

細かいことはさておき、この仕組みで、 /init に引数として、 /mnt/c/Windows/System32/notepad.exe が渡されて起動するんですね。なので、実際のプログラムの実行処理は /init に任されてると。WSL の /init は完全に Windows の息のかかったプロセスなので、あとは・・・。

この辺のことは、下の記事に詳しすぎるぐらいに書いてあるので、英語で長文読むのが嫌じゃない人は、是非、読みましょう。

blogs.msdn.microsoft.com

Ubuntu 16.04 on Windows 10 Insider Preview 14936

以前にこんな記事を書きました。

espresso3389.hatenablog.com

で、この時は、Ubuntu 14.04相当だったんですが、このたび、Insider Preview Build 14936で、Ubuntu 16.04が使えるようになりました!

blogs.windows.com

Windows Subsystem for Linux (WSL) Updates

WSL will install Ubuntu version 16.04 (Xenial) instead of Ubuntu 14.04 (Trusty) in an upcoming release. This change will apply to Insiders installing new instances (lxrun.exe /install or first run of bash.exe). Existing instances with Trusty will not be upgraded automatically. Users can upgrade their Trusty image to Xenial using the do-release-upgrade command. Full WSL release notes can be found here.


Read more at https://blogs.windows.com/windowsexperience/2016/09/28/announcing-windows-10-insider-preview-build-14936-for-pc/#ugJRhOzzAI4lbHBC.99

適当に翻訳すると、

WSL は、次のリリースから、Ubuntu 14.04 (Trusty) の代わりに Ubuntu 16.04 (Xenial) をインストールするようになります。
インサイダーの方が、新規インスタンスをインストールする(lxrun.exe /install もしくは、最初の bash.exe の実行)場合にこの変更が適用されます。
既存の Trusty のインスタンスは、自動的にはアップグレードされません。TrustyのイメージをXenialにアップグレードするには、 do-release-upgrade コマンドを使ってください。
...

みたいな。既存の14.04は、普通の Ubuntu のように、 do-release-upgrade すればいいわけですね。

f:id:espresso3389:20160930182310j:plain

最近は、cmd.exeのコンソール機能も地味にまともになってきて、悪くない感じになってきました。とりあえず、

chcp 65001

しておけば、Unicode対応になりますし、フォントもConsolasとかも選べるようにはなります(日本語が表示できなくなりますけど)。

もう、最近は、ちょっとLinuxサーバーに接続ぐらいだったら、 cmd.exe の上から直接作業しても困らないぐらいにはなってきましたね。良いことです。

Mediumを試してる

はてなブログはてなブログの批判をしていいのかわからないんだけど、まぁ、画像のアップロードが面倒だったり、はてな記法でずっとやってきたけど、最近markdownかなと思ってたけど、うーん、みたいなところで、そういや、Mediumってどうなんだろう?って試しましたよっていう感じです。

medium.com

今後、本気で移行するかどうかは今から考えます。

ディスプレイアームを導入した

うちの環境は、今は、

という、Dual 4K の環境です。
ビデオカードや、その他に関しては、以前に書いた、この記事から変わってません。

espresso3389.hatenablog.com

まぁ、LGの4K液晶が安かったので、とりあえず買ってみたら、すごく良かったので、今度は、本命と言われてる、EIZOのEV3237を買ってみるぜっていうノリだったんですが、まぁ、素晴らしい環境ですね。
27MU67-Bに関しては操作系(入力切替など)で不満はあったり、どうせなら、EV3237 2台っていう環境にしたかったりというのはあるんですが、それでも普段使っていて著しく不満を感じるということはありません。

で、この環境は素晴らしいんですが、問題としては、どちらの液晶も、スタンドが意外と大きいんですね。まぁ、スペースが邪魔。
そして、手元のスペースを確保しようとすると、当然、ディスプレイとの距離はそれなりにとる必要がある。
僕の家の机は、横180cm x 縦70cmなので、それなりの広さはあるのですが、さすがにこのサイズの液晶を2台も置くと狭くなります。

ということで、ディスプレイアームを導入しようということを思い立ちました。

エルゴトロン

ディスプレイアームといえば、エルゴトロンというぐらい、エルゴトロン以外は無視してもいいぐらいなんですが、まぁ、エルゴトロン純正品は高いですね。一台用のアームでも、15000円はくだらないですし、デュアルアームだと、30000円を超えます。
買えない額ではないですが、一瞬躊躇する額ではあります。

で、デュアルディスプレイ環境なので、デュアルアームの、45-248-026とか、45-245-026、いずれにしても、こいつらは、31.5インチの液晶には対応できないことになっていました。

なので、結果的には、シングルアームを2台置くという結論になるんですが、Amazonググる感じでは、45-241-026には、OEM品がいくつかあることに気づきました。

具体的には、Amazonベーシック シングルディスプレイマウントアームヒューレット・パッカード HP シングルモニターアーム BT861AAの2つ。

ただ、Amazonの方は、意外と安くない。純正品と比べても2000円ぐらいしか変わりません。それから比べるとHPの奴は5000円ぐらい安くて非常にお手頃感がある。

ということで、HPの奴を2台購入してみました。

設置

一人でやったら、クソほど大変でした。クーラーガンガン聞かせた部屋で設置を行ったのですが、汗だくになりました。
設置中に、アームを伸ばしてるところの写真なんですが、このアームの長さが仇になりました。

f:id:espresso3389:20160811172551j:plain

今まで、ディスプレイのケーブルは、全部、2mのものだったんですが、設置中に、2mのケーブルでは、PCまでケーブルが届かないことが発覚してしまいした。

どうやら、普通に設置する場合に比べて、+50cmぐらいはないとPCに届かなくなる可能性があるようです。

EV3237BK(7.8kg)は、やはり重いので、アームをかなりキツめに調整しないと、ゆっくりではありますが、落ちてきます。この辺の調整はちょっとだけ試行錯誤が必要でした。

ということで、一度、設置を中止して、涼しくなってから夕飯がてらに、3mのDisplayPortケーブルと、3mのUSB3.0ケーブルを買ってきました。ということで、設置完了は既に夜。

f:id:espresso3389:20160811224731j:plain

また、ケーブリングも意外と大変でした。まぁ、設置には時間がかかるのは覚悟した方がいいですね。

その他

あと、うちでは、 Windows Hello顔ログインは必須なので、わざわざUSB3.0もケーブルを買ってきたのですが、逆に、こいつらをディスプレイに軽くで良いので固定しておかないと、ディスプレイを動かすたびに落ちそうになったりするので、微妙に気を使わないといけなくなったのは事実です。

f:id:espresso3389:20160812002745j:plain

まとめ

さすがに、EV3237BKは、重くて、さらにデカいので、アームだから自由自在というわけにはいきませんが、それでも、液晶を手前に持ってくることができるようになったので、作業は格段にしやすくなりました。また、液晶の下は空洞になるので、物を置くスペースとして利用できる感じになりました。

まぁ、31.5インチの液晶を手前に持ってくると、威圧感が凄いのは事実ですね。一方で、27インチの27MU67-Bにはまるで威圧感を感じません。4インチ弱の差っていうのがここまで大きいのかって思い知らされます。

Dispatcher とか Queue とかメッセージループとか

GUIのコードを書いていると、時間のかかる処理をやりたくなることは多々あると思うんですが、それを普通に実行しちゃうとGUIが固まりますよね。

で、C#には、Taskという比較的簡単に使える道具がありまして、例えば、適当なスレッドで実行して、その結果だけを非同期で待つって感じのコードなら、

var result = await Task.Run(() => SomeHeavyCalculation());

という感じで、長時間ブロックしちゃう SomeHeavyCalculation なんて処理も簡単にバックグラウンドで実行出来ちゃいます。まぁ、単発の処理ならこれでほとんど解決。何にも問題なし。

ところが、次のような何らかのオブジェクトに対して処理を行う場合にはいろいろと注意が必要になります。

SomeSharedObject obj;

async Task ProcessAsync()
{
  // これ、安全かなぁ?
  await Task.Run(() => obj.SomeHeavyCalculation());
}

何が危険なのかといえば、このオブジェクトが、スレッドセーフ(複数のスレッド間で共有してOKかどうか)がどうかが怪しいかもしれないからです。

Task.Run は、その時その時で、スレッドプール(いくつかのスレッドをあらかじめ作っておいて、いつでもすぐ使えるようにしておく)内のいずれかのスレッドで実行を開始する感じなので、obj.SomeHeavyCalculation()はいつも同じスレッドで実行されるとも限りませんし、また、await中は、処理が別のメソッドに明け渡されるので、ProcessAsync中に、またProcessAsyncが呼ばれることは普通にあり得ます。つまり、二つのobj.SomeHeavyCalculation()が同時に動いてしまう可能性もあります。
これはヤバい。下手すると一発クラッシュ。そうじゃなくても、データが破壊される可能性があります。

これを適当に解決しようとすると、例えば、

SomeSharedObject obj;

async Task ProcessAsync()
{
  await Task.Run(() => {
    // obj へのアクセスを排他的にする(一度に一つしか実行できない)
    lock (obj)
    {
      obj.SomeHeavyCalculation();
    }
  });
}

みたいなコードを書く羽目になる。lock(モニター)を使って、この処理が同時に複数個動作しないようにしちゃうわけです。
まぁ、これが一か所なら別に大した問題にもならないでしょう。

ただ、この場合、あまりにも多くのProcessAsyncが呼ばれると、スレッドプール内のスレッドの枯渇という恐ろしい事態が待っている可能性があります。スレッドプールにいくつのスレッドが入っているかは環境依存ですが、例えば、16個しかスレッドがなかった場合、この関数が短い間に16回呼ばれると、すべてのスレッドを使い果たしてしまい、他の処理がまったく実行されないという事態に陥る可能性があります。

また、別の側面で考えると、この処理は、同時には一つしか動かないわけで、普通に考えて、スレッドの無駄遣いにもなるでしょう。どう考えてももったいない。

ディスパッチャー (Dispatcher)

さて、こういう問題を解決するには、昔ながらの定石があります。
それは、キュー(FIFO:First-In,First-Out)とか、イベントループとか、ディスパッチャーとかと言われるものです。
仕組みは簡単です。仕事をお願いする方は、キューにお願いしたい仕事を投入して、仕事を請け負う側は、キューに入っている仕事を古いものから順番に処理していくっていう、とってもローテクな方法です。

基本的に、Windowsにしろ、macOSにしろ、iOSにしろ、現代的なGUIのシステムは、GUI処理用に、このキューを持っていて、それをメッセージループだの、イベントループだの、あるいは、COMだと、STA(Single Thread Apartment)とか、そういう名前で呼んでいます。GUIは、基本的には、他のプログラムから投げられたメッセージを一つずつ解析して処理していくことを繰り返すだけです。
そのため、当然、一つのメッセージの処理に時間がかかったり、あるいは、このメッセージ処理の流れをせき止められたりすれば、GUIは固まってしまいます。最初に書いた話ですね。

じゃぁ、なんでそれが便利なのか。
上に書いた、別々のスレッドで同時に動かされてしまうかもしれない問題が解決するからです。一つずつしか処理しないので、当然ながら、同時に処理が動くなんてことはありません。

要は、先ほどの例でいえば、SomeSharedObject に対する処理を専任で行うディスパッチャーを一つ作ってしまえばいいんですね。作りましょう。それ。

で、実はこの手のものは、さっきも書いたとおり、OSが既に実装しています。Windowsなら、メッセージループがそれですし、WPFならば、Dispatcher というそのものズバリの名前のものがあります。

WPF の Dispatcher を使って実装してみる

要は、専用のスレッドを一つ立てて、そこに Dispatcher を置いておけば、さっきの SomeSharedObject に対する処理は全部、それでやれちゃうよねっていう感じです。

で、いきなり、SomeSharedObjectの実装の話になっちゃいますが、SomeHeavyCalculationを直接呼べないようにして、それをディスパッチャー経由で実行するのを、awaitできるようにしちゃいます。

class SomeSharedObject
{
  Dispatcher dispatcher;

  public SomeSharedObject()
  {
    ...
    // スレッドを起動して、そこで dispatcher を実行する
    var dispatcherSource = new TaskCompletionSource<Dispatcher>();
    var thread = new Thread(new ThreadStart(() => {
      dispatcherSource.SetResult(Dispatcher.CurrentDispatcher);
      Dispatcher.Run();
    }));
    thread.Start();
    dispatcher = dispatcherSource.Task.Result; // メンバ変数に dispatcher を保存
  }

  // 重い処理: 外からは直接呼べないようにしちゃう
  private void SomeHeavyCalculation() {...}

  // 外の人にはこっちを呼び出してもらおう
  public async Task SomeHeavyCalculationAsync()
  {
    await dispatcher.InvokeAsync(() => SomeHeavyCalculation());
  }
}

これで、使う人は、余計なスレッドとか考えなくてよくなりました。SomeHeavyCalculationAsync を await するだけで良いので、かなり簡単です。

内部的にも、SomeHeavyCalculationを無茶な呼ばれ方をしたりしなくなるので、心配事がかなり減りました。SomeHeavyCalculationは、必ず、単独でしか実行されません。スレッドの競合とか、面倒な話を考えなくて良くなりました。

みんな幸せ。

WPFのDispatcherの闇

まぁ、このコードには、ほとんど説明してないコンストラクタのコードにヤバい部分があります。
WPFのDispatcherの設計ミスとも言えるのですが、スレッド内で実行する Dispatcher を確実にメンバ変数に保存する部分に闇があります。 Task.Result とかいうあんまり使わない方が良いヤバい奴がいます。詳しくは「ConfigureAwait」とかで調べてみてくださいな。TaskCompletionSourceっていう謎の奴が出てきていますが、こちらもどうしても知りたければ調べてください。
このコンストラクタは、なるべく、await を使ってない、プログラムの最初の方で呼び出してね。さもなくば、プログラムが固まっちゃう(デッドロックしちゃう)っていう問題があるんです。悩ましい。

そして、もう一つ、特大級の闇があります。
WPFのプログラムは、そのプログラム内で実行しているすべての Dispatcher が完全に停止するまで、終了しません。つまり、この Dispatcher を殺さないと、プログラムを[X]ボタンとかで終了しようとしても終了できないのです。なんということだ・・・。

こちらには有効な解決策があります。簡単です。GUIのスレッドというか、フォアグラウンド・ディスパッチャーというか、呼び出し元の表で動いているDispatcherを監視して、そいつが終了するタイミングで、こちらのDispatcherも終了すればいいだけです。

これは、次のようにコンストラクターに書き足せばいいだけです。

class SomeSharedObject
{
  Dispatcher dispatcher;

  public SomeSharedObject()
  {
    ...
    // スレッドを起動して、そこで dispatcher を実行する
    var dispatcherSource = new TaskCompletionSource<Dispatcher>();
    var thread = new Thread(new ThreadStart(() => {
      dispatcherSource.SetResult(Dispatcher.CurrentDispatcher);
      Dispatcher.Run();
    }));
    thread.Start();
    dispatcher = dispatcherSource.Task.Result; // メンバ変数に dispatcher を保存

    // 表のディスパッチャーが終了するタイミングで、こちらのディスパッチャーも終了する
    Dispatcher.CurrentDispatcher.ShutdownStarted += (s, e) =>
      dispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
  }
  ...
}

iOS/macOSGrand Central Dispatch (GCD)

iOS/macOSにも、Grand Central Dispatchといわれる同様の仕組みがあります。なんか、仰々しい名前ですが、やってることはまるで同じです。こちらの方がWPFよりも便利なのは、スレッド込みで作りこまれているものが用意されていることです。

class SomeSharedObject
{
  DispatchQueue dispatcher = new DispatchQueue("SomeSharedObjectQueue");

  ...

  // 重い処理: 外からは直接呼べないようにしちゃう
  private void SomeHeavyCalculation() {...}

  // 外の人にはこっちを呼び出してもらおう
  public async Task SomeHeavyCalculationAsync()
  {
    await InvokeAsync(() => SomeHeavyCalculation());
  }

  // DispatchQueueをawaitできるようにするラッパー(戻り値を返すことのできるバージョン)
  public async Task<T> InvokeAsync<T>(Func<T> func)
  {
    var tcs = new TaskCompletionSource<T>();
    dispatcher.DispatchAsync(() => {
      try
      {
        tcs.SetResult(func());
      }
      catch (Exception e)
      {
        tcs.SetException(e); // ここで例外をちゃんとキャッチしておく(※下の追記を参照)
      }
    });
    return await tcs.Task;
  }

  // DispatchQueueをawaitできるようにするラッパー(Action用)
  public async Task InvokeAsync(Action action)
  {
    await InvokeAsync(() => { action(); return 0; });
  }
}

全体的には、WPF版よりも簡単なコードであることがわかるでしょう。

※追記(2016/08/13) GCD における例外の扱い

重要なことを書き忘れていたので追記しておきます。
まぁ、例のごとく、上にあるコードは、全部、サンプルなので、いろいろ現実的なことを言い出せば追記することはたくさんあるのですが、
XamarinのDispatchQueue.DispatchAsyncは、内部的には、例外をそのままディスパッチャー内にぶちまけちゃう実装になっているんですが、これについては、

https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/developer.apple.com

には、以下のように、例外とか投げたら知らんぜって書かれています。

IMPORTANT
GCD is a C level API; it does not catch exceptions generated by higher level languages. Your application must catch all exceptions before returning from a block submitted to a dispatch queue.

試しに、上記の InvokeAsync で呼び出すActionの中で例外を放ったりすると、

=================================================================
Got a SIGABRT while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries 
used by your application.
=================================================================

みたいな感じでアプリごと死にます。
なので、例外の処理は明示的にやってやる必要があります。

まとめ(適当)

ここまで見れば、WPFiOS/macOSで同じインターフェイスでこのディスパッチャーを実装できそうなことはすぐにわかると思います。実際にできます。

また、ここでは書いていませんが、Androidでも、Handlerというものを使って、ほぼ同等の事が簡単にできます(どこかで暇になったら加筆します)。

要は、どこのプラットフォームでも、基本的には、同じ考え方が通用するということです。そこが一番重要です。