Skip to main content
GameDev.net gamedev.net
🔒 Locked

PeekMessage changed in Windows 10?

Started by ProgrammerDX Aug 13, 2015 at 11:55 AM 12 replies 12.8k views
Original Post
ProgrammerDX
ProgrammerDX

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,

rave3d
rave3d

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

ProgrammerDX
ProgrammerDX

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.

rave3d
rave3d

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.

SmkViper
SmkViper
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.
wintertime
wintertime

OP might be confused by the SendMessage vs PostMessage handling?

samoth
samoth

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, , , , , , 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.

Erik Rufelt
Erik Rufelt

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..

ProgrammerDX
ProgrammerDX

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.

SmkViper
SmkViper

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
Erik Rufelt
Erik Rufelt

I think you misunderstood samoth's post.. at least unless I completely missed the point. He says it's easy to get an impression from MSDN that couldn't possibly be true..

SmkViper
SmkViper

I think you misunderstood samoth's post.. at least unless I completely missed the point. He says it's easy to get an impression from MSDN that couldn't possibly be true..


If so then my sarcasm meter is broken today... (which is entirely possible, it's been a long week)
Brain
Brain




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).

In my experience the hung app notification is triggered if an app hasn't responded to any posted message within around 10 seconds.

You'll also notice that C++ programs built with the debug profile in visual studio may not trigger the hung app notification whilst release mode programs do.

This is again one of those things that are undocumented and I noticed this whilst debugging a badly behaved loading screen.

Just thought you might find this useful! :)

Khatharr
Khatharr

I initially encountered this behavior in Win7 when I found that I couldn't catch certain messages outside of the proc. (It looks like you may be trying to do the same thing I was trying to do, actually.)

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
rave3d
rave3d

I can understand how this confusion can be happen.

There are lots of tutorials out in the internet using PeekMessage just as the OP does. Most of them never mention that PeekMessage automatically dispatches the messages.

So someone start as a little programmer having even difficulties with the language itself and relies on the tutorial and it just works.

If you use something for years in a specific way and it works, you are never going to read API documentation until the first time it fails.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.