模态对话框引发的窗口过程重入问题

首先,先普及下什么是模态对话框,模态对话框的话就是不能在对话框和该程序的其他窗口之间进行切换,必须结束该对话框才可以进行其他操作。

可以使用DialogBox创建一个模态对话框,至于为啥这个对话框会造成上面的原因呢,其实msdn上有那么一段解释:

The DialogBox macro uses the CreateWindowEx function to create the dialog box. DialogBox then sends a WM_INITDIALOG message (and a WM_SETFONT message if the template specifies the DS_SETFONT or DS_SHELLFONT style) to the dialog box procedure. The function displays the dialog box (regardless of whether the template specifies the WS_VISIBLE style), disables the owner window, and starts its own message loop to retrieve and dispatch messages for the dialog box. 

When the dialog box procedure calls the EndDialog function, DialogBox destroys the dialog box, ends the message loop, enables the owner window (if previously enabled), and returns the nResult parameter specified by the dialog box procedure when it called EndDialog. 

在第一段的最后末尾,提到了会禁用父窗口,并启动自己的消息循环来检索和分发对话框的消息。

第二段阐述了使用EndDialog关闭对话框时,会结束自己的消息循环并启用父窗口。

这里的启用禁用父窗口可以使用EnableWindow函数,该函数可以启用或禁用指定窗口或控件的鼠标和键盘输入。我们可以其实可以在WM_INITDIALOG中调用该函数启用父窗口得到验证。

好了,因为上面并不是想要记录的重要,所以简单概括下,重点在于自建的消息循环,此时可能会造成一些窗口过程重入带来的问题,所谓的窗口过程就是回调(消息处理函数WinProc),看下面的案例

#include 

LRESULT CALLBACK WinProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASS wndClass = {0};
    wndClass.lpfnWndProc = WinProc;
    wndClass.lpszClassName = TEXT("keyTest");
    
    RegisterClass(&wndClass);
    
    HWND hwnd = CreateWindow(TEXT("keyTest"), TEXT("keyBoard"),
        WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL,NULL,hInstance,NULL);
    
    ShowWindow(hwnd,nCmdShow);
    UpdateWindow(hwnd);
    
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    
    return 0;
}

LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_KEYDOWN: //输入法得切换英文
        {
            TCHAR szBuff[MAXBYTE] = { 0 };
            if (wParam >= 'A' && wParam <= 'Z')
            {
                wsprintf(szBuff, TEXT("OnKeyDown: %c\r\n"), wParam);
                MessageBox(hwnd, szBuff, NULL, MB_OK); //第一处
            }
            OutputDebugString(szBuff);
            return 0;
        }
        case WM_CHAR:
        {
            TCHAR szBuff[MAXBYTE] = { 0 };
            if (wParam >= 'a' && wParam <= 'z')
            {
                wsprintf(szBuff, TEXT("OnChar: %c\r\n"), wParam);
                MessageBox(hwnd, szBuff, NULL, MB_OK);//第二处
            }
            OutputDebugString(szBuff);
            return 0;
        }
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
    }
    return DefWindowProc(hwnd,uMsg,wParam,lParam);
}

在上面的代码中,有两处MessageBox,第一处在击键消息中,第二处在于字符消息中。那么我们应该先来谈谈键盘的消息处理,

在消息循环中有这么一行代码

TranslateMessage(&msg);

查看MSDN我们可知,该函数用于将击键消息翻译成一个新的字符消息,并将该字符消息放到当前线程的消息队列中。注意这个字符消息是新生成的,该并不会改变传入参数的msg。

也就是说,当我键盘是英文小写字母状态时,会产生一个A的击键消息,然后翻译该消息后又生成了一个a的字符消息。

好了,然后我们看代码,通过上面的分析,是不是按理来说,应该会是先弹出WM_KEYDOWN中的弹窗,然后再弹出WM_CHAR中的弹窗。

运行结果是相反的,先弹出了第二处,然后再弹出了第一处。你可能会想到是不是翻译完后先发送了WM_CHAR再发送了WM_KEYDOWN消息呢?

不不,其实调试下可知,其流程是没问题的,先进入了WM_KEYDOWN消息,然后在MessageBox的时候,神奇的事情发生了,流程又转到了WM_CHAR消息,然后弹窗,最后返回到WM_KEYDOWN消息的弹窗。

为什么会这样?这个问题开始也困扰了我好几天。下面分析一下

MessageBox是个模态对话框,那么对于模态对话框,其内部就会有自己的消息循环,那么对于一些输入型消息都会被该MessageBox先处理,而一些其余消息会根据其消息体(msg.hwnd)发送给对于的窗体处理。

OK,模拟下流程,当我们按下a键,先产生了击键消息,后经过翻译后产生了字符消息(该消息属于主窗体),那么先来到WM_KEYDOWN,里面调用了MessageBox,此时,主窗口的消息循环进入MessageBox的消息循环,在该MessageBox的消息循环中接收到了字符消息,该消息属于主窗体,分发消息后那么又回到该主窗体的回调函数中的WM_CHAR(此时窗口过程被重入),里面又调用了MessageBox,所以此时消息循环又转移了,那么只有当WM_CHAR中的MessageBox结束后才返回到WM_KEYDOWN中的MessageBox,KEYDOWN结束后才回到了主程序的消息循环。

与上面问题类似的简单说法就是,当一个窗口过程(回调)调用为其发送其他消息的函数时,这种情况下,在该函数调用返回前,窗口过程必须将第二个消息处理完,窗口过程才处理前一条消息,也就是相当于同步,顺序流程处理消息。

在大多数情况下,窗口过程的重入并不会带来什么问题,但是我们需要做到心中有数,知道可能会有该情况的发生。

你可能感兴趣的:(Windows编程)