Visual C++中消息回调函数的执行顺序

3

我正在开发一款Windows Visual C++应用程序,该程序将监视消息泵以便检测各种事件。以下是我的主cpp文件的框架:

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

int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) {
    HWND hwnd;
    WNDCLASSEX wincl;

    // register WindowProcedure() as message callback function
    wincl.lpfnWndProc = WindowProcedure;
    // assign other properties...

    if (!RegisterClassEx (&wincl))
        return 0;

    // create main window
    hwnd = CreateWindowEx ( ... );

    // infinite message loop
    while (GetMessage (&messages, NULL, 0, 0)) {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    }

    return 0;
}

以下是回调函数的框架:

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    switch(message) {
        case WM_CLIPBOARDUPDATE:
            // handle the update here
    }
}

我的简单问题是回调函数是否保证按照顺序开始和结束,或者它们可能会与一些重叠的并行执行?换句话说,两个对回调函数的调用是否可能并行执行,可能导致竞争条件?或者Windows是否保证每条消息按照串行方式处理,一次处理一个?欢迎提供任何文档或参考资料。


并不是特别关于你的问题,不过你应该从WinMain中返回messages.wParam,而非0。当你调用PostQuitMessage(0)来处理例如WM_DESTROY时,那个"0"(传递给PostQuitMessage的参数)将会是在GetMessage-loop退出后messages.wParam的值。 - jensa
2个回答

4
窗口消息被存储在队列中。每次调用GetMessage时,它会从队列中移除第一个消息。当您调用DispatchMessage时,将调用窗口过程。
因此,是的,消息是按顺序处理的。但是,如果在窗口过程内部调用SendMessage,则可能存在一些重叠,因为该函数绕过消息队列直接调用窗口过程(与PostMessage不同,后者只是将消息放入队列中)。
但这并不意味着窗口过程将并行执行(即来自多个线程)。无论是DispatchMessage还是SendMessage都不会创建不同的线程来运行窗口过程。

我问这个问题的原因是我正在使用多线程,需要知道当前正在处理哪个消息。值得庆幸的是,C++似乎按顺序处理消息。 - Tim Biegeleisen
@TimBiegeleisen:回答你的第一个问题:是正确的,但不完整。你可能没有直接调用SendMessage,但你调用的Windows API会这样做。另外值得一读的是:线程何时可以接收窗口消息? 这篇文章说明了另一种重入的方法。@jensa:SendMessage不会切换到另一个线程。调用线程只是被置于睡眠状态,然后操作系统的线程调度从那里开始。调用线程仍然可以分派传入的消息和发送的消息。 - IInspectable
@IInspectable 调用线程被放置/阻塞,直到窗口的窗口过程完成处理消息(窗口过程在拥有/创建窗口的线程上下文中执行)。来自MSDN:“如果指定的窗口是由不同的线程创建的,则系统会切换到该线程并调用相应的窗口过程。”“发送线程被阻塞,直到接收线程处理消息。”但正如您所说:然而,在等待其消息被处理时,发送线程将处理传入的非排队消息。 - jensa
@IInspectable 对于我的使用情况,我主要关注的是发生在Windows剪贴板上的更新。这些更新有没有可能以无序或并行的方式进行处理?我应该缩小我的用例范围以避免混淆。 - Tim Biegeleisen
@TimBiegeleisen: WM_DESTROYCLIPBOARD“当调用EmptyClipboard函数清空剪贴板时,发送到剪贴板所有者。” 换句话说,剪贴板也可能存在重入问题。 - IInspectable
显示剩余8条评论

1

窗口消息有时存储在消息队列中,有时则不会。不存储在队列中的窗口消息示例包括WM_ACTIVATE、WM_SETFOCUS和WM_SETCURSOR。其他消息,如WM_PAINT,则被放置在消息队列中。

窗口的窗口过程从未并行调用(请参见上一个答案)。

这些消息确实按顺序处理,GetMessage从队列中提取第一个消息。然而,需要注意的一点是,消息可以在不被处理的情况下“消失”于队列中。例如,Windows在每个窗口中内部维护一个结构(PAINTSTRUCT),以跟踪窗口的无效区域。只要窗口的客户区的任何部分无效,Windows就会将WM_PAINT消息放置在消息队列中。但是,如果无效区域被验证(例如通过调用ValidateRect),Windows实际上会从队列中删除该消息。此外,如果另一个区域被添加到无效区域中,Windows不会在队列中放置另一个WM_PAINT消息,而是使用更新后的区域更新已经放置的WM_PAINT消息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接