SendMessage とゾンビ
dispoff.exe は、モニタの電源を切ると、すぐさま終了するアプリ・・・のつもりだったが、Process Explorer で見ると、プロセスが生き残っているのが確認できた。なぜだろう。ExitProcess() まで呼んでるのに終了しないとは。
ただし、いつでもゾンビになるわけではなく、何らかの条件が整ったときに、こうなるようだ。
いつでも再現するんなら、いろいろなコードを試して、ああでもないこうでもないと試行錯誤ができるのになあ。と。
ためしに Explorer.exe を落とした状態で dispoff.exe を起動してみたら、再現できたので、デバッグ開始。
バグ再現ソース
再現するソースは以下。
#define UNICODE #pragma comment(lib, "user32.lib") #include <windows.h> void __start__() { // program will start from here if `gcc -nostartfiles` ExitProcess(WinMain(GetModuleHandle(NULL), 0, "", 0)); } int WINAPI WinMain(HINSTANCE hi, HINSTANCE hp, LPSTR cl, int cs) { SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, 2); return 0; }
これを gcc -nostartfiles 略
でビルドして、Explorer.exe を落としてから実行すると、ゾンビになる。
あやしいところといえば、-nostartfiles である。そこで、ふつうにビルドしたのだが、それでも再現した。
ということは、SendMessage() が原因なはず・・・だが、引数をいろいろ変えて実行してもだめだった。
API を変えたらバグが消えた
そういえば、メッセージ送信 API には、PostMessage() もあるな、と思い出し、それに替えてみたら、ゾンビにならず、終了するようになった。
それぞれの API の特徴として、大雑把に引用すると、以下である。
"メッセージを処理し終わった後で、制御を返します。"
"メッセージを処理するのを待たずに制御を返します。"
SendMessage の弱点
SendMessage は、送信先のウィンドウが、メッセージを処理し終わるまで待つのである。HWND_BROADCAST で、すべてのトップレベルウィンドウに送信しているので、おそらく、それらすべてウィンドウがメッセージを処理し尽くさないと、dispoff.exe に制御が戻らないということになる。
ゾンビになっているということは、すなわち、誰かがメッセージを処理していないということだ。HWND_BROADCAST だと、それが誰なのか特定するのに手間がかかる。というか、どうやって特定すればいいのか分からん。そういうツールもないことはないんだろうけど。
つぶやき
そもそも、メッセージを処理してないってどういうことなんだろう。ウィンドウプロシージャは存在するけど、GetMessage() や DispatchMessage() などを用いて、メッセージキューを消化しない・・・みたいなタイプのプロセスでもいるのだろうか。あるいは、WM_SYSCOMMAND 受信時に、ウィンドウプロシージャが return 0 をしないとかかな? たぶんそれだと、DefWindowProc() 呼び忘れっぽいよな。・・・はてさて。メッセージについての仕様とか、あまりよくわかってないので、いらん推測を書くのはやめておこう。(書いてから言うな)
PostMessage による解決
でまあ、PostMessage() のほうは、送信先ウィンドウがメッセージを処理するかどうかなんて放置して、さっさと自身を終了するようになっている。これによって、理論上は WinMain 関数もさっさと終了するし、実際、終了してるので、PostMessage のそうした挙動が効いているのだろう。
SendNotifyMessage による解決
さらに、メッセージ送信 API には SendNotifyMessage というものもある。
"メッセージの処理を終えるのを待ちません。"
これが素晴らしいものである。名前は SendMessage に似ているが、PostMessage と同様に、送信先ウィンドウプロシージャにメッセージを送信すると、すぐに終了するようになっている。
SendNotifyMessage は、PostMessage よりも素晴らしい。なぜならば、PostMessage は、メッセージキューの最後にメッセージを格納するのに対して、SendNotifyMessage は、SendMessage と同じく、ウィンドウプロシージャを直接呼び出すからである。
まー、どちらの実装にしても、体感できるほどの差はなかったが、理論上は、SendNotifyMessage のほうが相手に素早くメッセージを食わせられるということだ。
デバッグ済みコード
というわけで、以下のコードをもって、デバッグ完了とした。
#define UNICODE #pragma comment(lib, "user32.lib") #include <windows.h> void __start__() { // program will start from here if `gcc -nostartfiles` ExitProcess(WinMain(0, 0, NULL, 0)); } int WINAPI WinMain(HINSTANCE hi, HINSTANCE hp, LPSTR cl, int cs) { SendNotifyMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, 2); return 0; }
__start__
の中身もちょっと変えているが、デバッグとは関係ない。