作業から なかなか帰れないプログラム
[ 2013 / 07 / 05 ]
Windows C++ プログラムの構造は
前処理
// メイン メッセージ ループ:
while( GetMessage( &msg, NULL, 0, 0 ) ) {
TranslateMessage( &msg );
DispatchMessage( &msg );
}
後処理
LRESULT CALLBACK WindowProc(
と なっているのは ご存じの通りですが 通常なら WindowProc の何かにヒットして 処理や作業が終われば 再び
GetMessage( &msg, NULL, 0, 0 ) の所で止まっていて 新しいメッセージが入れば DispatchMessage( &msg )
によって WindowProc が呼ばれて 又 処理をして メッセージを待つ この繰り返しで 常に GetMessage の所で
待てれば 直ぐに メッセージが出た時に処理が始まり さくさく動作する事になります。
但し 処理や作業 が常にすぐに終了するとは限りません。例えばループ処理で何十と言うファイルをコピーするとか
又 コピーする相手が遅いとか 有るフォルダー以下の 何百と言うファイルをチェックするとか インターネットの
先の URL を確認するとか となれば 処理や作業に飛んだ時点で そこのコードに張り付いているので GetMessage が
出来ません。そうなると ウィンドウズは固まったようになり 動きは止まり 又 他からの メッセージにも反応が
無くなってしまいます。時間のかかる作業で キャンセルボタンを作ってもこのボタンからのメッセージにも対応
出来ないので キャンセル もうまく行きません。又 途中経過を表示する為に どこかにテキストを書いたつもりでも
その時点で即 WM_PAINT が出る API を使用しなければ 書いたつもりでも 表示は変わりません。
こんな状態を打破する為には 時間のかかる処理をする繰り返しの中に 新しいメッセージを確認する API
GetMessage はメッセージか来るまでそこで処理が止まってしまうので使用できませんが 無くても処理がすぐに帰る
PeekMessage 等を入れて 適宜 メッセージを確認する事にすれば キャンセル処理を始められたり 途中経過も自然に
表示出来る様になります。但し 取得するメッセージは何を取得するのか 取得する時 メッセージキューに残すのか
消すのかどのメッセージを DispatchMessage して流すのか流さないのか良く考えてしないと終了して戻った時に
不自然な事になったり 最悪の場合 尻切れトンボ的な終わり方をしてしまうかも知れません。
最後に 同じようにそこで留まってコードは進んでいないのですが 通常良く使用する TrackPopupMenu でメニューを
出している時は 結構特殊例で 全てとは言いませんが システムの方で都合の良い様に 適宜 メッセージを取得して
配分してくれるのは使用中に感じる事が出来ると思います。
[ 2013 / 12 / 15 追記 ]
PeekMessage でのメッセージを流して適宜処理をする作業 ( メッセージポンプ とも言うらしい ) をもっと スマートに
効率良く出来ないかと思い MSDN を見ていたら何か 恐ろしい事が書いて有りました。
「wMsgFilterMin と wMsgFilterMax の各パラメータでどのような値を指定した場合でも、PeekMessage は必ず
WM_QUIT メッセージを取得することに注意してください。」
と 言う事は 07/15 に書いた様な 「良く考えてしないと終了して戻った時に不自然な事になったり 最悪の場合
尻切れトンボ的な終わり方をしてしまうかも知れません。」 が より可能性を持って起こりうる事になりそうです。
WM_QUIT メッセージ は PostQuitMessage で出て 上記の while( GetMessage( &msg, を ブレークさせます。
此を出す所の ウィンドウプロシージャーは
case WM_DESTROY: // 自分の終わり
PostQuitMessage( 0 ); // メッセージループも終了
break;
とするのが通常です。此で PeekMessage で WM_QUIT を 処理 削除 してしまうと メイン メッセージ ループ の
while( GetMessage() がそこで永遠にストップして ウィンドウは削除されたのに プロセスは残って終了させるには
タスクマネージャーのお世話になるしか 終了方法が無くなってしまいます。
此を避ける方法ですが
1.WM_QUIT を確認したら PeekMessage をやめて 再度 PostQuitMessage をして帰る。
( あまりかっこ良くないし 本当に此で働くかは疑問。)
2.PeekMessage 中は WM_QUIT が出ない様にする。
( より正確には WM_QUIT が キューに無い事を確認した上で )
PostQuitMessage をさせない → WM_DESTROY メッセージが出ない様にする。
→DestroyWindow をさせない。→ WM_CLOSE を受け付けない。
の二つで 避ける 方法は 上記の色々な段階で 工夫出来るとは思いますが PeekMessage を 使用する時には
よりいっそうの慎重さが必要かも知れません。
ただ 他のスレッドからは ( 他の プログラムからは ) 直に DestroyWindow は出来ずに 終了させるには
PostMessage( hWnd, W_CLOSE になるので 此の辺りの メッセージのやり取りコードと 終了方法を整理して ガード
しておけば 特に問題は出なくなる事にもなるかと思います。
[ 2018 / 07 / 25 追記 ] GetMessage の取得順位について
ページのタイトルは 作業から なかなか帰れないプログラム なのですが この部分の内容は どちらかと言うと
他のプログラムを 待つ時の為の覚え書きです。他に 適当なタイトルで新設しても良かったのですが 同じ
GetMessage ループという事で此処に追記として書いておきます。
以前から 他のプログラムや デスクトップのウィンドウが表示されるのを 待つ時や 様子が変化するのを待つ時に
自分のスレッドを最小時間待って 又 確認 コードを呼び出す コードにすれば
case WM_APP:
何らかの確認コード;
if ( 実現していないなら ) {
Sleep( 16 );
PostMessage( hWnd, WM_APP, , );
}
break;
と 言う様な事になるのでしょうか 此は Sleep() で長い時間止めてしまうより 現実的には 最短時間で確認できるし
確認する時間も プログラムで調整できるので 結構重宝な物だとは思うのですが 自分でマウスボタンでとかキー入力で
途中でやめたい コードを入れる時にはちょっとした罠が有ります。
メッセージ キュー は必ずしも 入った順に取得出来るとは限らないのです。MSDN によれば取り出す順序は
送信済みメッセージ
ポスト済みメッセージ
入力(ハードウェア)メッセージとシステムの内部イベント
送信済みメッセージ(再度)
WM_PAINT メッセージ
WM_TIMER メッセージ
の 順序になり PostMessage で廻し出すと 入力(ハードウェア)メッセージとシステムの内部イベント は後回しですから
結局 GetMessage をしているだけでは マウスメッセージも キーメッセージも PostMessage が廻っている間は入らない
事に なってしまいます。こんな時の為に MSDN には
ポスト済みメッセージより先に入力メッセージを取得するには、wMsgFilterMin と wMsgFilterMax の各パラメータを
使って取得。と書いて有ります。結局
// メイン メッセージ ループ:
while( GetMessage( &msg, NULL, 0, 0 ) ) {
TranslateMessage( &msg );
DispatchMessage( &msg );
}
だけではダメで PostMessage で時間稼ぎをするコードが有るプログラムは 適宜
if ( PeekMessage ( ( &msg, wMsgFilterMin, wMsgFilterMax, PM_REMOVE ) ) {
必要な処理
}
を 必要なフィルターを付けてメッセージ ループ 内に 入れないといけない事になります。
此の事が解り 対処しだしたのは 割と最近の事で 以前から 何か順序がおかしいとか コード通りに動かない 感じがして
いたのは 此の事だったと気がつきました。やはり 書かれた物が有る時には きちんと最後まで読んで理解しないと
いけない様です。