PeekMessage changed in Windows 10?

Started by
12 comments, last by rave3d 8 years, 8 months ago

Hey all,

I noticed in Windows 10 some oddities with PeekMessage. It's doing undocumented things.

One PeekMessage call would send MULTIPLE messages to the WndProc and then return zero (no messages according to msdn).

That's weird because:

1. It's processing more than one message in one PeekMessage call

2. It's returning zero

You can test this by creating a default Win32 app in Visual Studio, and then making the following simple changes:


/*(global var)*/ bool exitapp = false;
 
//.... in _tWinMain
    // Main message loop:

    while( !exitapp )

    {

        OutputDebugStringA( "CALL 1\n" );



        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

        {

            if( msg.message == WM_QUIT )

            {

                return msg.wParam;

            }



            char tmp[256];

            wsprintfA( tmp, "PEEKED %d\n", msg.message );

            OutputDebugStringA( tmp );



            if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

            {

                TranslateMessage(&msg);

                DispatchMessage(&msg);

            }

        } else {

            OutputDebugStringA( "none\n" );
        }

        

        OutputDebugStringA( "CALL 2\n" );

    }
//...
 
//... in WndProc at the top

    char tmp[256];

    wsprintfA( tmp, "WndProc %d\n", message );

    OutputDebugStringA( tmp );
 
//...
   case WM_CLOSE:
       exitapp = true;
       break;

Anyone know what's going on? it messes with my engine that expects PeekMessage to process only one message.

Greetings,

Advertisement

Unfortuneatly i can't access a Win10 system at the moment.

If in interpret your problem correctly, you say PeekMessage returns Zero even there was a Message?

In that case your WndProc would never be called, becaus normally it would be called by DispatchMessage functions.

How do you find that PeekMessage sends multiple messages to WndProc, as far is a know the MSG struct it can only hold one message, so it's not possible to send more than one message at a time.

Are you shure there are not just some } at the wrong place? because this: OutputDebugStringA( "CALL 2\n" ); in your quote above is in the else branch, wich will be accesses if PeekMessage returns 0

Hm no the OutputDebugStringA( "none\n" ); would only be called in the else branch, I've added branches to make that code more clear for you.

An example 'strange' output would be:


CALL 1
WndProc 274
WndProc 16
WndProc 28
WndProc 134
WndProc 147
WndProc 147
WndProc 145
WndProc 146
WndProc 146
WndProc 6
WndProc 641
WndProc 642
WndProc 7
none
CALL 2

It's the PeekMessage itself that is calling my WndProc, multiple times for different messages inside one PeekMessage call, and then returns zero. It doesn't even get to DispatchMessage because it returns zero. This means PeekMessage is dispatching messages on its own now.

You can test this on Windows 10. I'll test again on Windows 7 later if it's the same.

After reading documentation of PeekMessage again, i think it behaves exactly like documented (also not how i thougt till now):

The first sentence of documentation says:

Dispatches incoming sent messages, checks the thread message queue for a posted message, and retrieves the message (if any exist).

So it should be probably the same on Win7

As an Idea you probalby could try what will happen if you call PeekMessage with PM_NOREMOVE specified, and remove the message by yourself using GetMessage, but I'm not sure if this has the effect you need.

In which way the behavior of PeekMessage messes with your engine? perhaps there is a more elegant way to handle this.

Nothing in the documentation says that PeekMessage cannot call your WndProc (in fact it specifically says it dispatches messages) or that it will only dispatch one message at a time (and in fact uses "messages", plural, indicating more than one).

Also, I believe you misunderstand the return value. A return value of 0 means messages are not available in the queue (aka, the queue is empty), a return value of non-zero means more messages are available to be processed. The return value does not tell you if any messages were dispatched by PeekMessage - obviously because you would have been told via your WndProc smile.png

Sounds like you were relying on undocumented behavior - MS can change the way any function behaves as long as it still conforms to the documentation.

OP might be confused by the SendMessage vs PostMessage handling?

According to the to-the-letter wording of MSDN, this is correct behavior. However, I'm not sure that this is indeed the complete, untainted truth (MSDN might be lying).

If you translate those numbers into message names, they are: WM_SYSCOMMAND, WM_CLOSE, WM_ACTIVATEAPP, WM_NCACTIVATE, <unknown>, <unknown>, <unknown>, <unknown>, <unknown>, WM_ACTIVATE, WM_IME_SETCONTEXT, WM_IME_NOTIFY, WM_SETFOCUS.

For each of them (except the ones for which I failed to look up the number), MSDN verbatim states "is sent when...". Now, the little word "sent", means using "SendMessage" as opposed to "PostMessage", which means "message is not queued, call blocks until message has been processed". PeekMessage, on the other hand, dispatches these messages (and checks whether there's at least one queued, too), so that would be behavior exactly as specified.

However, if that was really true, then whoever sends e.g. a WM_ACTIVATE would be blocked indefinitely until my program processes the message (if, for example, my window procedure is broken in some way, what then? What if i don't ever call GetMessage or PeekMessage?).

Now, who is sending WM_ACTIVATE? How are windows being activated? Usually because the user Alt-Tabs into them or clicks with the mouse. Following the above logic, if that was really the case, I should be able to block the mouse update thread (system-wide) by placing a case WM_ACTIVATE: for(;;){} or similar in my window proc.

Obviously, that is not the case. What an exploit... I can't really believe that system-critical components (like input methods, or the mouse handling subsystem) rely that you properly process messages and will block forever if you don't. They really, really, really, should be posting messages, not sending them.

From SendMessage docs: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644950%28v=vs.85%29.aspx

If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine. If the specified window was created by a different thread, the system switches to that thread and calls the appropriate window procedure. Messages sent between threads are processed only when the receiving thread executes message retrieval code. The sending thread is blocked until the receiving thread processes the message. However, the sending thread will process incoming nonqueued messages while waiting for its message to be processed. To prevent this, use SendMessageTimeout with SMTO_BLOCK set. For more information on nonqueued messages, see Nonqueued Messages.

Some messages are sent to WndProc from CreateWindow, some maybe from ShowWindow (I think..).

The messages that can block badly are probably posted to the thread, whereas the ones sent on starting the app for example are probably sent by a separate thread that handles the starting of that particular program (cursor turns to hour-glass and the app has 10 seconds I think to set up properly and become response, and otherwise you get "unresponsive" notifications if there's a window and waiting is stopped).

SendMessage can return errors (which could happen if a thread crashes while waiting for example).

There is (or used to be?) some option in Windows to let explorer open windows in different threads or processes or something that could change responsiveness..

Thanks for the answers everyone. It also behaves similar in Windows 7. It's just that in Windows 10, closing a Window through the taskbar (right-clicking to Close Window) sends a few more messages to the Window than on Windows 7 or 8 (to be more specific: sending WM_ACTIVATE after a WM_CLOSE, Windows 7 didn't do that), which bugged my program due to a design mistake with implementing PeekMessage (expecting it to handle one message per call was the mistake).

I fixed it now by putting specific WndProc messages into a std::queue and handle them in FIFO order after a PeekMessage call.

For each of them (except the ones for which I failed to look up the number), MSDN verbatim states "is sent when...". Now, the little word "sent", means using "SendMessage" as opposed to "PostMessage", which means "message is not queued, call blocks until message has been processed". PeekMessage, on the other hand, dispatches these messages (and checks whether there's at least one queued, too), so that would be behavior exactly as specified.


The use of the word "sent" does not imply that at all. Keep in mind that Windows itself doesn't even have to use Send/PostMessage at all. It has access to the code behind everything.

However, if that was really true, then whoever sends e.g. a WM_ACTIVATE would be blocked indefinitely until my program processes the message (if, for example, my window procedure is broken in some way, what then? What if i don't ever call GetMessage or PeekMessage?).


If you never call GetMessage/PeekMessage then Windows treats your process as hanged as asks the user to terminate it. Nothing in Windows actually relies on you handling your messages in a timely manner. That's why it's a message queue in the first place instead of direct method calls (which would have problems anyway because you'd be crossing process boundaries with all the memory fun that entails).

I'm not entirely sure of the point of your post... You're basically trying to make some sort of assumption about how Windows works internally based on things that the documentation doesn't even promise - and then saying MS is lying because you can find exceptions to the wrong assumptions you made in the first place?

MS is only beholden to fulfill the contracts specified by the API in MSDN. In some cases, yes, MSDN can get a little carried away in "why" something happens, but it should never be interpreted as it "has" to happen that way.

Again, MS is free to change the internals of Windows at any time, as long as they don't violate the Win32 contract.

If you start trying to make assumptions on internal Windows behavior and coding against those assumptions instead of the API, you're going to quickly end up as yet another stellar example on Raymond Chen's blog. smile.png

This topic is closed to new replies.

Advertisement