14

私はWin32の初心者であり、ユーザーがウィンドウのタイトルバーをつかんで画面上で移動すると、イベント中に Windows がプログラムのフローをブロックするという問題 (問題と呼べる場合) を追求してきました。

私には、この問題を解決する正当な理由はありません。いくつかの可能性として、フレームを完全に削除することが含まれますが、これは不便なハックのようです。一部のゲーム (シングル プレイヤー) では、これがまったく問題にならないことがあります。ただし、マルチプレイヤー ゲームでは、プログラムがフリーズすると問題が発生する可能性があることを読みました。これは、プログラムが継続的な情報の流れを期待しており、そのような遅延の後に圧倒される可能性があるためです。

これを自分に追加しようとしましたWindowProc

switch (uMsg)
{
    case WM_SYSCOMMAND:
        if (wParam == SC_CLOSE)
            PostQuitMessage(0);

        return 0;
    ...
    ...
    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;

そして、これは簡単なハックのように思えますが、閉じるアイコンの上にマウスを置いたときに、プログラムを閉じずにマウスを離して放すことができ、その間に閉じるアイコンを押したままにすると、プログラムが再びブロックされます。

さらに、ユーザーがタイトルバーをクリックしてマウスをドラッグしたときにウィンドウを移動するために必要なコードを手動で含める方法がわかりません。uMsgまず、 とのどちらを処理すればよいかわかりませんwParam

私の質問は、ユーザーが終了ボタン (または最小化/最大化ボタン) をクリックしたときにブロックを禁止する方法と、マウスがボタン上でクリックされて離されたときにケースを処理する方法です。プログラムをブロックせずにウィンドウを移動/ドラッグするユーザー (または、ボタンやメニューではなくタイトルバーをクリックしたときに送信されるメッセージ)?

でウィンドウを作成していますWS_SYSMENU | WS_MINIMIZEBOX

プログラムが最小化、最大化、および終了コマンドに応答するようにしたい。

マルチスレッディングで解決できれば面白いのですが、シングルコア プロセッサで動作させることができるかどうかは疑問です。フックについて読んだことがありますが、MSDN ページはまだ解釈が難しいです。

4

3 に答える 3

27

アプリがフリーズする理由 — メッセージ ループとスレッドの概要

この現象は、特定のメッセージに限定されません。これは、Windows メッセージ ループの基本的な特性です。1 つのメッセージが処理されている場合、他のメッセージを同時に処理することはできません。このように正確に実装されているわけではありませんが、アプリがメッセージをキューから引き出して、挿入されたのとは逆の順序で処理するキューと考えることができます。

したがって、メッセージの処理に時間がかかりすぎると、のメッセージの処理が中断され、アプリケーションが事実上フリーズします (入力を処理できないため)。この問題を解決する唯一の方法は明らかです。1 つのメッセージの処理に時間をかけすぎないことです。

多くの場合、これは処理をバックグラウンド スレッドに委譲することを意味します。メイン スレッドですべてのメッセージを処理する必要があり、バックグラウンド ワーカー スレッドは終了時にメイン メソッドに報告する必要があります。GUI とのやり取りはすべて単一のスレッドで行う必要があり、ほとんどの場合、それがアプリケーションのメイン スレッドになります (これが、多くの場合、UI スレッドと呼ばれる理由です)。

(そして、あなたの質問で提起された反論に答えるために、はい、シングル プロセッサ マシンで複数のスレッドを操作できます。必ずしもパフォーマンスの向上が見られるわけではありませんが、UI の応答性が向上します。ここでのロジックは、スレッドができるということです。一度に 1 つのことしか実行できませんが、プロセッサはスレッド間を非常に高速に切り替えて、一度に複数のことを効果的にシミュレートできます。)

この MSDN の記事で、さらに役立つ情報を入手できます: Windows アプリケーションでのハングの防止

特殊なケース: モーダル イベント処理ループ

Windows での特定のウィンドウ操作は、モーダル操作です。モーダルとはコンピューティングにおける一般的な用語で、基本的にユーザーを特定のモードにロックすることを指し、モードを変更する (つまり、そのモードから抜け出す) まで他の操作を行うことはできません。モーダル操作が開始されるたびに、別の新しいメッセージ処理ループがスピンアップされ、モードの期間中、(メインのメッセージ ループの代わりに)そこでメッセージ処理が行われます。これらのモーダル操作の一般的な例は、ドラッグ アンド ドロップ、ウィンドウのサイズ変更、およびメッセージ ボックスです。

ここでのウィンドウのサイズ変更の例を考えると、ウィンドウはメッセージを受け取り、デフォルトの処理のためWM_NCLBUTTONDOWNにそれを渡します。ユーザーが移動またはサイズ変更操作を開始しようとしていることがわかり、Windows 独自のコードの奥深くにある移動/サイズ変更メッセージ ループに入りました。したがって、新しい移動/サイズ変更モードに入ったため、アプリケーションのメッセージ ループは実行されなくなりました。DefWindowProcDefWindowProc

ユーザーが対話的にウィンドウを移動/サイズ変更している限り、Windows はこの移動/サイズ変更ループを実行します。これは、マウス メッセージをインターセプトし、それに応じて処理できるようにするためです。移動/サイズ変更操作が完了すると (たとえば、ユーザーがマウス ボタンを離すかEscキーを押すと)、制御がアプリケーション コードに戻ります。

このモード変更がメッセージで発生したこと通知されることに注意してください。対応するメッセージは、モーダル イベント処理ループが終了したことを示します。これにより、アプリケーションが処理できるメッセージを生成し続けるタイマーを作成できます。これがどのように実装されているかの実際の詳細は比較的重要ではありませんが、簡単に説明すると、独自のモーダル イベント処理ループ内でアプリケーションにメッセージをディスパッチし続けます。関数を使用してメッセージに応答してタイマーを作成し、関数を使用してメッセージに応答してタイマーを破棄します。WM_ENTERSIZEMOVEWM_EXITSIZEMOVEWM_TIMERDefWindowProcWM_TIMERSetTimerWM_ENTERSIZEMOVEKillTimerWM_EXITSIZEMOVE

ただし、完全を期すためにそれを指摘するだけです。私が作成したほとんどの Windows アプリでは、これを行う必要はありませんでした。

では、私のコードのどこが悪いのでしょうか?

それとは別に、質問で説明した行動は異常です。Visual Studio テンプレートを使用して新しい空の Win32 アプリケーションを作成した場合、この動作を再現できるとは思えません。ウィンドウ プロシージャの残りの部分を見ないと、(上記で説明したように) メッセージをブロックしているかどうかはわかりませんが、質問で確認できる部分は間違っています。自分で明示的に処理しないメッセージは常に呼び出す必要があります。DefWindowProc

この場合、あなたはそれをやっていると思い込んでしまうかもしれませんが、WM_SYSCOMMANDその にはさまざまな値を設定できますwParam。そのうちの1 つだけを処理しSC_CLOSEます。あなたがreturn 0. これには、すべてのウィンドウの移動およびサイズ変更機能 ( SC_MOVESC_SIZESC_MINIMIZESC_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 プログラミング モデルの理解が深まるまで、近づかないでください。フックによって提供される機能が必要になるのは、まれなケースです。

于 2013-08-04T13:30:54.463 に答える
1

送信されたすべてのメッセージを印刷すると、ブロックが発生する前に最後に送信されたWindowProcように見えます。WM_NCLBUTTONDOWNこのイベントが発生した後にマウスの位置を確認することもできますが、単純な問題を解決するには不便な方法のようです。

于 2013-08-04T10:17:30.863 に答える