Windows版メンテナはうろたえないッ!仮にファイルディスクリプタの構造が変化することがあっても、Rubyが操作しないといけないメンバは遥かむかーしのバージョンからある極めて基本的な部分であり、その部分が変化する可能性は極めて低い。実際、バージョンアップで変化したのは、構造体の後ろの方に新しいメンバが追加されただけであり、Rubyがアクセスしている古くからあるメンバはそのまま構造体の前の方に残されていた。いうことは、ファイルディスクリプタ構造体のサイズが検出できさえすれば、あとはそれを利用して自前で計算してやれば、知りたい情報にはアクセスできることになる。わけで、set_pioinfo_extra()関数がそのファイルディスクリプタ構造体のサイズを検出する処理なのである。具体的には、構造体のサイズを推測しながら公開関数を用いてファイルディスクリプタの中身を変更してやり、狙ったオフセットでその変更結果が検出されれば、推測した構造体のサイズが正しかったのだろう、という手法を用いて構造体のサイズを検出している。ところで、このサイズ検出処理を実行時に行っているのは、CランタイムバイナリはRubyのビルド後に変更されるかもしれないし、またビルド環境と異なるCランタイムがインストールされた環境にRubyバイナリがインストールされる可能性もあるためである。えーと、RUBY_MSVCRT_VERSIONが80未満の場合のSetErrorMode()呼び出しというのは、これはWindowsのAPIなのだが、なにか致命的なエラー(普通はメモリ破壊系)が起きたときにOSが出しちゃうメッセージボックスを抑止するための指定である。うーん、これ、80以上でも有効にすべき気がちらりほらり。get_version()というのは実行中のOSのバージョンを取得する関数である。なんでOSのバージョンを取得しておく必要があるかというと、OSのバージョンによって処理を変更しないといけない場合があるので......。実際は可能な限りそのようなケースはバージョンで見るんじゃなくて実際の機能の有無を確認して処理を分けているのだが(UNIX系OSでならconfigure時に検出していることを、Windowsの場合はバイナリ配布が多いだろうと想定して実行時にやってると思ってもらっていい)、どうしても外的な確認では決定できずにOSバージョンで判定しないといけないものもある(例えば、あるAPIで特定の引数の値を受け付けるかどうかがOSバージョンによって違うことがある)ので、そのために取得している。なんかまだちょっと見ただけなのに、結構長くなってしまった。SP1でそれが起こった。次はw32_cmdvector()(win32/win32.c)という関数なのだが、この関数のコードを読んでいく前に、前提知識を一つ共有しておくことにする。それは「Windowsではシェルはワイルドカードを展開しない」ということである。Windowsのシェルといえばcmd.exeで、Rubyも基本的にはそれを前提としている。最近だとPowerShellを使う人も増えてきているのかもしれないが(Windowsでコマンドラインインターフェースを使う人は減ることこそあれ増えることはなさそうなのでそんなことないという気もするが)、いずれにせよ、Windowsのシェルはワイルドカードを展開はしない。いう人もいたりするわけだが、これはワイルドカード展開した結果がdirコマンドに渡されているのではなくて、dirコマンドが内部でワイルドカードを展開しているのである。dirコマンドはシェル内部コマンドなので、それって同じことなんじゃねーの、と思う人もいるかと思うが、同じくシェル内部コマンドであるechoコマンドに*あたりを渡してみれば、言ってることの意味が理解してもらえると思う。いうわけで、シェルはワイルドカードを展開しない。内部コマンドですら、定められたコマンドの場合しかワイルドカードを解釈しないのに、外部コマンドに渡す引数でワイルドカードを展開してくれるなど、もちろんあろうはずもない。そんなわけで、Windowsでは与えられたコマンドライン引数のワイルドカードを展開するとかしないとかいうのは、各コマンド(アプリ)の責務である。なにそれクソい......とか思われちゃうと困っちゃうのだが、こうやって責務を各アプリに押し付けちゃった代わりに、Windowsではちゃんとワイルドカード展開をサポートする機能を提供している。一つはOSのAPIであるFindFileシリーズAPI。一つはCランタイムの付属品であるsetargv.objシリーズである。FindFileシリーズAPIというのは、引数でワイルドカードを含む文字列を与えたら、それを展開して実際のファイルにマッチした結果を次々と返してくれるAPIである。いわゆるglobbing処理をOSがAPIとして提供してくれている。setargv.objシリーズというのはもっと便利なもので、コンソールアプリをビルドする際にこのobjファイルをリンクすると、コマンドライン引数に含まれるワイルドカードが自動的に展開された状態でargvに格納されてからmain()関数が呼ばれるようになる、という優れものである。Windowsでは、アプリ作者側が、自分のアプリに必要な場合だけワイルドカード展開処理をくっつけることができる、という選択の自由を提供してくれているわけである。......そんなうまい話で終わるんだったら「闇」とか言うわけないよねー。FindFileシリーズAPIも、setargv.objシリーズも、理解するワイルドカードメタ文字は*と?だけである(後者は中で前者を利用しているので当たり前だが)。}とか**とかを知っちゃってる現代人には、ちょっとこれは許しがたいものがあるだろう。RubyのDir.globはこれらのメタ文字を解釈してglobbingを行ってくれるのに、コマンドライン引数ではそれを理解しないとか言われちゃうと、普通は「ちょ、なにそれ」と思うものであろう。いうわけで、RubyのWindows特別処理では、RubyのAPIを利用して自前でコマンドライン引数のglobbingをやっちゃおう、ということが行われている。それがこのw32_cmdvector()という関数なのである。うは、前提を語るだけで長くなっちゃった。実際のコード読みは次回からとしよう。rb_w32_sysinit()からw32_cmdvector()の実際のコード(win32/win32.c)をいよいよ見ていくことにする。w32_cmdvector()を呼び出す際には、引数として、GetCommandLineW()の結果、CP_UTF8、rb_utf8_encoding()の結果、を渡している。String)のポインタとして返す。Characterとは、簡単に言っちゃえばUnicode、もう少しだけ深く言えばUTF-16LE(UCS-2という説もあり)な「文字」のことである。Cでの型はWCHARで、符号なし16bit整数。*ということは、つまり、ここで取得されるのは要素ごとに分割されたargv形式の配列ではなく、実際にコマンドラインでユーザーが指定した文字列そのものである。コマンドラインの分割処理もWindowsでは本来は各アプリの責務なのである。普通はCランタイムのスタートアップコードがargvに分割してくれてはいるし、CommandLineToArgvW()というAPIもあるのでそれを使うこともできるのだが、Rubyでは独自の追加ルールを用いてコマンドラインを分割するので(具体的には単一引用符(')の扱い)、これらは利用しない。次のCP_UTF8というのは、コマンドライン解析結果として期待されるエンコーディングの指定である。CP_UTF8の意味はUTF-8コードページなのだが、コードページというのは、えーと、Windowsにおけるエンコーディング情報みたいなもの、ということでいいかな。その次のrb_utf8_encoding()はRubyのUTF-8エンコーディング情報を返す関数だが、これもやっぱり解析結果として期待されるエンコーディングの指定としてw32_cmdvector()に渡している。なんでUTF-8コードページとRubyのUTF-8エンコーディング情報という2つのエンコーディング情報を渡す必要があるのだろうか?まず、Rubyインタプリタとしては、コマンドライン解析結果は当然ながらRubyの理解できるエンコーディングの文字列で頂きたい。String、つまりUTF-16LEからUTF-8への変換方法を知らない。Windowsはもちろんその方法を知っている(APIがある)が、WindowsはRubyのエンコーディング情報を理解しない。いうわけで、WindowsにUTF-8に変換させるためにコードページが必要であり、また一方で変換してできた文字列がUTF-8であることをRubyに教えるためにRubyのエンコーディング情報もまた必要となるわけである。実は全くその通りで、Init_Encoding()もInit_String()もまだ呼ばれていないのだからRubyのStringオブジェクトとしてエンコーディングをいじるような機能はまだ使えない。例の3つの組み込みエンコーディングの実装自体は(何しろ組み込まれてるんだから)すでに存在する。StringオブジェクトとしてではなくCレベルの文字列として操作する分には、その文字列のエンコーディングが何か、ということさえわかっていて、StringクラスのAPIを経由したりせずに組み込みエンコーディングの実装に含まれるプリミティブな関数を呼び出したりする範囲内であれば問題ない(*1)のである。......なんかここまででいつもの長さくらいになっちゃってるのだが、前回はコード読んでないし今回はまだほんの1行を説明しただけなので、心を鬼にして突き進むことにする。それでは、w32_cmdvector()だ。