アプリがフリーズする理由 — メッセージ ループとスレッドの概要
この現象は、特定のメッセージに限定されません。これは、Windows メッセージ ループの基本的な特性です。1 つのメッセージが処理されている場合、他のメッセージを同時に処理することはできません。このように正確に実装されているわけではありませんが、アプリがメッセージをキューから引き出して、挿入されたのとは逆の順序で処理するキューと考えることができます。
したがって、メッセージの処理に時間がかかりすぎると、他のメッセージの処理が中断され、アプリケーションが事実上フリーズします (入力を処理できないため)。この問題を解決する唯一の方法は明らかです。1 つのメッセージの処理に時間をかけすぎないことです。
多くの場合、これは処理をバックグラウンド スレッドに委譲することを意味します。メイン スレッドですべてのメッセージを処理する必要があり、バックグラウンド ワーカー スレッドは終了時にメイン メソッドに報告する必要があります。GUI とのやり取りはすべて単一のスレッドで行う必要があり、ほとんどの場合、それがアプリケーションのメイン スレッドになります (これが、多くの場合、UI スレッドと呼ばれる理由です)。
(そして、あなたの質問で提起された反論に答えるために、はい、シングル プロセッサ マシンで複数のスレッドを操作できます。必ずしもパフォーマンスの向上が見られるわけではありませんが、UI の応答性が向上します。ここでのロジックは、スレッドができるということです。一度に 1 つのことしか実行できませんが、プロセッサはスレッド間を非常に高速に切り替えて、一度に複数のことを効果的にシミュレートできます。)
この MSDN の記事で、さらに役立つ情報を入手できます: Windows アプリケーションでのハングの防止
特殊なケース: モーダル イベント処理ループ
Windows での特定のウィンドウ操作は、モーダル操作です。モーダルとはコンピューティングにおける一般的な用語で、基本的にユーザーを特定のモードにロックすることを指し、モードを変更する (つまり、そのモードから抜け出す) まで他の操作を行うことはできません。モーダル操作が開始されるたびに、別の新しいメッセージ処理ループがスピンアップされ、モードの期間中、(メインのメッセージ ループの代わりに)そこでメッセージ処理が行われます。これらのモーダル操作の一般的な例は、ドラッグ アンド ドロップ、ウィンドウのサイズ変更、およびメッセージ ボックスです。
ここでのウィンドウのサイズ変更の例を考えると、ウィンドウはメッセージを受け取り、デフォルトの処理のためWM_NCLBUTTONDOWN
にそれを渡します。ユーザーが移動またはサイズ変更操作を開始しようとしていることがわかり、Windows 独自のコードの奥深くにある移動/サイズ変更メッセージ ループに入りました。したがって、新しい移動/サイズ変更モードに入ったため、アプリケーションのメッセージ ループは実行されなくなりました。DefWindowProc
DefWindowProc
ユーザーが対話的にウィンドウを移動/サイズ変更している限り、Windows はこの移動/サイズ変更ループを実行します。これは、マウス メッセージをインターセプトし、それに応じて処理できるようにするためです。移動/サイズ変更操作が完了すると (たとえば、ユーザーがマウス ボタンを離すかEscキーを押すと)、制御がアプリケーション コードに戻ります。
このモード変更がメッセージで発生したことが通知されることに注意してください。対応するメッセージは、モーダル イベント処理ループが終了したことを示します。これにより、アプリケーションが処理できるメッセージを生成し続けるタイマーを作成できます。これがどのように実装されているかの実際の詳細は比較的重要ではありませんが、簡単に説明すると、独自のモーダル イベント処理ループ内でアプリケーションにメッセージをディスパッチし続けます。関数を使用してメッセージに応答してタイマーを作成し、関数を使用してメッセージに応答してタイマーを破棄します。WM_ENTERSIZEMOVE
WM_EXITSIZEMOVE
WM_TIMER
DefWindowProc
WM_TIMER
SetTimer
WM_ENTERSIZEMOVE
KillTimer
WM_EXITSIZEMOVE
ただし、完全を期すためにそれを指摘するだけです。私が作成したほとんどの Windows アプリでは、これを行う必要はありませんでした。
では、私のコードのどこが悪いのでしょうか?
それとは別に、質問で説明した行動は異常です。Visual Studio テンプレートを使用して新しい空の Win32 アプリケーションを作成した場合、この動作を再現できるとは思えません。ウィンドウ プロシージャの残りの部分を見ないと、(上記で説明したように) メッセージをブロックしているかどうかはわかりませんが、質問で確認できる部分は間違っています。自分で明示的に処理しないメッセージは常に呼び出す必要があります。DefWindowProc
この場合、あなたはそれをやっていると思い込んでしまうかもしれませんが、WM_SYSCOMMAND
その にはさまざまな値を設定できますwParam
。そのうちの1 つだけを処理しSC_CLOSE
ます。あなたがreturn 0
. これには、すべてのウィンドウの移動およびサイズ変更機能 ( SC_MOVE
、SC_SIZE
、SC_MINIMIZE
、SC_RESTORE
などSC_MAXIMIZE
) が含まれます。
WM_SYSCOMMAND
そして、自分自身を処理する正当な理由は本当にありません。DefWindowProc
あなたのためにそれを世話させてください。処理する必要があるWM_SYSCOMMAND
のは、ウィンドウ メニューにカスタム項目を追加したときだけです。その場合でも、認識できないすべてのコマンドを に渡す必要がありますDefWindowProc
。
基本的なウィンドウ プロシージャは次のようになります。
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CLOSE:
DestroyWindow(hWnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
メッセージ ループが間違っている可能性もあります。慣用的な Win32 メッセージ ループ (関数の下部近くにありますWinMain
) は次のようになります。
BOOL ret;
MSG msg;
while ((ret = GetMessage(&msg, nullptr, 0, 0)) != 0)
{
if (ret != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// An error occurred! Handle it and bail out.
MessageBox(nullptr, L"Unexpected Error", nullptr, MB_OK | MB_ICONERROR);
return 1;
}
}
どんな種類のフックも必要ありません。これらに関する MSDN のドキュメントは非常に優れていますが、そのとおりです。複雑です。Win32 プログラミング モデルの理解が深まるまで、近づかないでください。フックによって提供される機能が必要になるのは、まれなケースです。