Win32 message handling questions

Started by
5 comments, last by wintertime 7 years, 2 months ago

Hey guys,

As part of some larger profiling and optimization work I have been looking into how I handle messages in my Win32 app. My code for handling windows messages is probably mostly copy-pasted from some tutorial or book I read many years ago (ie. I don't remember exactly) and I haven't really thought much about it until now.

So, the way I handle windows messages is to call this method every frame:


void Win32RenderWindow::messagePump()
{
    MSG msg;
    while(PeekMessage(&msg, mHandle.windowsHandle, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

That's pretty standard as far as I know. My window procedure looks like this:


LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_CREATE)
    {
        // Store a pointer to the window which sent this message.
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)(((LPCREATESTRUCT)lParam)->lpCreateParams));
        return 0;
    }
	
    // Get the stored window pointer by window handle.
    Win32RenderWindow* window = reinterpret_cast<Win32RenderWindow*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
	
    if(window)
        return window->msgProc(msg, wParam, lParam);
    else
        return DefWindowProc(hwnd, msg, wParam, lParam);
}

If there is a render window, the proc forwards the call to the window. Otherwise the default proc is called. The render window method looks something like this:


LRESULT Win32RenderWindow::msgProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        /* ... handle app specific messages here ... */
    }

    return DefWindowProc(mHandle.windowsHandle, msg, wParam, lParam);
}

Again, fairly standard stuff. Inside the switch I handle things like window resize events etc. These are some observations I have made about the system and I would like to ask about them:

  • The call to Win32RenderWindow::messagePump() can sometimes take a long time (10ms or so). It might be enough to move the mouse very quickly back and forth over the render window to generate a lot of messages. Is it normal for the processing to take this long? According to my profiler the time is spent inside PeekMessage() and DefWindowProc() and almost no time is spent anywhere else.
  • I always though that, under normal circumstances, the call to the message procedure (MainWndProc()) is done through the call to DispatchMessage() but it seems like PeekMessage() is calling it too, and 99% of the time spent inside MainWndProc() and is through PeekMessage() rather than DispatchMessage(). Again, is this normal?
  • Why is PeekMessage() calling the window procedure at all? Isn't it just supposed to get the message of the queue and give it to you so you can handle it by calling TranslateMessage() and DispatchMessage()?

Thanks!


EDIT: After some further investigation it seems like DefWindowProc() can call back to the main window proc (This was news to me too, I did not know that could happen), which might explain why it looks like so much time is spent inside DefWindowProc().

Advertisement

This is actually referenced in the documentation. PeekMessage:

During this call, the system delivers pending, nonqueued messages, that is, messages sent to windows owned by the calling thread using the SendMessage, SendMessageCallback, SendMessageTimeout, or SendNotifyMessage function. Then the first queued message that matches the specified filter is retrieved. The system may also process internal events. If no filter is specified, messages are processed in the following order:


https://msdn.microsoft.com/en-us/library/ms644943(VS.85).aspx


Basically my guess is that the default proc is posting more messages to your queue, then they're being handled when you call PeekMessage, since the documentation states that peekmessage will dispatch sent messages before it grabs posted messages to return to you. As for why it's taking so long to process them all, I wouldn't know. You might want to see what messages it is sending, it's possible it might be getting itself into some kind of processing loop because you aren't handling something correctly. It shouldn't take 10 ms to handle messages.

Yes, I did read the documentation, however I am not sure that's what it means. The very line you quoted says it "delivers pending, nonqueued messages" and "Then the first queued message that matches the specified filter is retrieved". To me that sounds like the function gets the messages from some OS-level system into the application message queue, and then it gives you the first message in the queue. That still says nothing about actually processing that message.

That said, the docs also say "The system may also process internal events." whatever that means, and the whole page starts with "Dispatches incoming sent messages" which may or may not have anything to do with the "dispatching" done by DispatchMessage().

Maybe I am just reading this wrong but I still don't think it should actually run the winproc for any messages, just give you the message so you can do it. I mean, otherwise what prevents messages from being double-processed?

Ah, I knew I remembered from my reading of Raymond Chen that specifying a window handle in PeekMessage was dangerous. Here you go: https://blogs.msdn.microsoft.com/oldnewthing/20050209-00/?p=36493

Many system services create windows on your behalf. For example, if input method editing is enabled, the method editor may create helper windows to assist in character input. If you initialize COM, then COM may decide to create a helper window to assist in inter-thread marshalling. If you use only a filtered GetMessage, then messages destined for these helper windows will never be retrieved, and you will be left scratching your head wondering why your program occasionally hangs when it tries to perform a drag/drop operation, for example.

Seems like you may be hitting a timeout caused by one of those "occasional hangs" - I suggest specifing NULL instead of a window handle in your PeekMessage call and see if that resolves your problem.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

That's interesting but it does not seem to help. Much like in a previous post of mine (https://www.gamedev.net/topic/685089-vsync-messing-with-my-job-system/) the problem only seems to occur when I have VSync on. With VSync off the most time Win32RenderWindow::messagePump() has ever taken is 0.8 ms.

Are you by any chance swapping buffers inside your message handler (WM_PAINT)? If the swap is in there and happens to wait for vblank it could explain your observed behavior.

If you are not handling WM_ERASEBKGND and WM_PAINT then depending on how you set up the window the GDI may draw stuff in DefWindowProc, which might interfere with your drawing.
I remember a while ago I was catching both, set a boolean if necessary, did a ValidateRect on WM_PAINT and used the correct return values to pretend drawing was done, without calling DefWindowProc.

This topic is closed to new replies.

Advertisement