このブログをブックマークしている奇特な人がいるらしい。最近は、ほとんどネタも書いてないというのに。まあこういうときは、例の如く、oldnewthingから話のネタを引っ張ってくるのがいいだろう。幸いにして、いま面白そうなネタを書いている。
How are window manager handles determined in 16-bit Windows and Windows 95?
How are window manager handles determined in Windows NT?
Why is the limit of window handles per process 10,000?
ただし、この話はいわゆるimplementation detailというものなので、絶対に悪用しないように。Raymond Chen様がブログを止めてしまうかもしれない。
今回は、ウインドウハンドルの値についてだ。oldnewthingを知っているようなプログラマなら誰しも、32bitなWindowsにおけるカーネルオブジェクトへのハンドルというのは、単にプロセスのハンドルテーブルのオフセットに過ぎないと言うことを知っているはずだ。しかしウインドウハンドルの値はどうか。16bit時代の頃、ウインドウハンドルの値というのは、ウインドウマネージャのセグメントへのnearポインタだったのだ。そのため、ウインドウハンドルと言うのは、16bitの値であった。
nearポインタとfarポインタ。私はこんな世界に生まれなくて幸いであった。当時は、メモリと言うのは64KBで境されるセグメントだったのだ。16bitなアドレッシングで扱えるメモリの量は64KBである。だから、64KBより大きいメモリを扱いたかったり、別のプロセスのメモリを指定したい場合は、FARポインタを使う必要があった。FARポインタは32bitで、上位16bitは、セグメントへのアドレスであった。当時はこのセグメントを複数使いこなすことが、プログラマに求められていた。必要ならば、DSを自分で書き換えることは普通であった。この頃のポインタは、本当に難しかったに違いない。
やがて時代は移り、32bitの世界がやってきた。Windows 95である。プロセスが複数のでかいヒープを持つことが当たり前になる時代。しかし、世の中はまだ16bitコードであふれかえっていた。そこで、Windowsは16bitコードをサポートする必要があった。そこで、WIN16のウインドウハンドルが問題になる。その頃、ウインドウハンドルの値は16bitである。したがって、Windoes 95でも、ウインドウハンドルの値は16bitでなくてはいけなかった。しかし、Windows 95 のポインタは32bitだ。一体どうすればいいのか。
そこで、ウインドウマネージャは、メモリを古きよきmovableとして確保する。得られるメモリは、実際のアドレスではなくHLOCALというハンドルだ。このとき、ウインドウマネージャは、32bitのヒープマネージャーに、16bitハンドルを返すように要求する。ヒープマネージャは64KBのメモリを確保し、実際のメモリポインタを格納しておき、そのメモリへのオフセットを返す。これで、16bitなウインドウハンドルが作られる。
ポインタひとつにつき4バイト必要なので、この64KBのハンドルでは、最大16384個のハンドルを扱える。CreateWindowExのドキュメントで
Windows 95/98/Me: The system can support a maximum of 16,384 window handles.
と書かれているのはこのためだ。実際には、ゼロはNULLと混同されるために使われなかったりするので、もう少し少ないそうだが。
しかし、ポインタは4バイトなのだから、あらかじめ4で割ることにしておけば、65535個のウインドウハンドルを扱えるはずである。何故しないのかと疑問を呈する人がいるかも知れぬ。しかし、Windows 3.1はたかだか700程度のウインドウしか扱えなかったことを考えると、すでに23倍も多いのである。その頃は、数百のウインドウと言うのは物凄く多いと考えられていた。メモリは現在のように十分ではなく、クールな奴等は8MB積んでいた。
しかし、この実装はある問題を引き起こした。同じウインドウハンドルの値が再利用されやすいと言うことである。すでに破棄されたウインドウハンドルを使おうとすれば、当然エラーになる。ところが、そのウインドウハンドルが、再利用されると、別のウインドウを指すようになる。つまりまったく別のウインドウに対して操作を行うようになる。これは、操作する側にとってもされる側にとっても、いい状況ではない。
そこで、Windows NTでは、対策を施した。ウインドウハンドルの上位16bitを活用するのである。Windows 95は上位16bitをゼロにしていたが、NTでは、ユニークな値を入れるようにしている。これは、ウインドウハンドルが再利用されるたびにインクリメントされる値である。こうすることによって、現実的な間、まったく同じ値のウインドウハンドルが使われないようにしている。16bitコードには、例の如く下位16bitだけを渡せば良い。
NTの時代には、だいぶメモリ容量も緩和されたが、ウインドウハンドルの数はどの程度緩和されたのだろうか。NULLなどの特殊な値を除外しても、65000個ほどはウインドウハンドルを扱えるはずである。しかし、NTのウインドウハンドルは32700程度に制限されている。なぜかというと、どうやら世の中には、ウインドウハンドルの値は偶数であると決め付けているプログラムがあるらしい。
また、1プロセスあたり10000個のウインドウハンドルしか開けないが、この制限は何のためにあるかと言うと。そんなに使うべきじゃないということらしい。すでに最大値の三分の一も使っているのだ。そんなプログラムは絶対におかしい。あまりにもウインドウが多すぎると、タスクマネージャが起動できなくなり、ユーザがそんなおかしなプロセスを強制終了できなくなってしまうかもしれない。まあ、この辺は納得できる。
さて、さっそくこの情報を試すことにしよう。ウインドウハンドルの、意味のある値は下位16bitである。しかも、偶数であるというassumptionにより(assumption、なんといういい響き!) 最下位ビットも使われていない。即ち我々は、ウインドウハンドルを0x0000fffeでANDしても問題ないはずである。早速試そう……動いた。どうやら、上位16bitがゼロのときは、ユニークな値にする対策を無視してくれるらしい。今の実装では。
ここに書いてあることを絶対に悪用しないように。
人柱さんのブログをRSSに登録させていただいている奇特な者ですw
ReplyDelete密告の開発(改良?)はもうされないんでしょうか?
とか言ってみたり・・・