Date: | July 27, 2005 / year-entry #204 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20050727-16/?p=34793 |
Comments: | 17 |
Summary: | Last time, I alluded to weirdness that can result in the normal cycle of destruction messages being thrown out of kilter. Commenter Adrian noted that the WM_GETMINMAXINFO message arrives before WM_NCCREATE for top-level windows. This is indeed unfortunate but (mistake or not) it's been that way for over a decade and changing it now would... |
Commenter Adrian noted that the But that's not the weirdness I had in mind. Some time ago I was helping to debug a problem with a program that was using the ListView control, and the problem was traced to the program subclassing the ListView control and, through a complicated chain of C++ objects, ending up attempting to destroy the ListView control while it was already in the process of being destroyed. Let's take our new scratch program and illustrate what happens in a more obvious manner. class RootWindow : public Window { public: RootWindow() : m_cRecurse(0) { } ... private: void CheckWindow(LPCTSTR pszMessage) { OutputDebugString(pszMessage); if (IsWindow(m_hwnd)) { OutputDebugString(TEXT(" - window still exists\r\n")); } else { OutputDebugString(TEXT(" - window no longer exists\r\n")); } } private: HWND m_hwndChild; UINT m_cRecurse; ... }; LRESULT RootWindow::HandleMessage( UINT uMsg, WPARAM wParam, LPARAM lParam) { ... case WM_NCDESTROY: CheckWindow(TEXT("WM_NCDESTROY received")); if (m_cRecurse < 2) { m_cRecurse++; CheckWindow(TEXT("WM_NCDESTROY recursing")); DestroyWindow(m_hwnd); CheckWindow(TEXT("WM_NCDESTROY recursion returned")); } PostQuitMessage(0); break; case WM_DESTROY: CheckWindow(TEXT("WM_DESTROY received")); if (m_cRecurse < 1) { m_cRecurse++; CheckWindow(TEXT("WM_DESTROY recursing")); DestroyWindow(m_hwnd); CheckWindow(TEXT("WM_DESTROY recursion returned")); } break; ... } We add some debug traces to make it easier to see what is going on. Run the program, then close it, and watch what happens. WM_DESTROY received - window still exists WM_DESTROY recursing - window still exists WM_DESTROY received - window still exists WM_NCDESTROY received - window still exists WM_NCDESTROY recursing - window still exists WM_DESTROY received - window still exists WM_NCDESTROY received - window still exists WM_NCDESTROY recursion returned - window no longer exists Access violation - code c0000005 eax=00267160 ebx=00000000 ecx=00263f40 edx=7c90eb94 esi=00263f40 edi=00000000 eip=0003008f esp=0006f72c ebp=0006f73c iopl=0 nv up ei ng nz na pe cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000283 0003008f ?? ??? Yikes! What happened?
When you clicked the "X" button, this started the window destruction
process.
As is to be expected,
the window received a if (IsWindow(hwndToDestroy)) { DestroyWindow(hwndToDestroy); }
At any rate, the recursive call to
And that's why we crash.
The base
I intentionally chose to use the new scratch program
(which uses C++ objects) instead of the classic scratch program
(which uses global variables) to highlight the fact that
after the recursive Moral of the story: Understand your window lifetimes and don't destroy a window that you know already to be in the process of destruction. |
Comments (17)
Comments are closed. |
Why can’t DestroyWindow assert in this recursive case to give the developer more clues?
I’ve seen so many people call DestroyWindow in WM_DESTROY. It really makes me wonder where they are getting the idea that this is the right thing to do. MSDN has some incorrect information, but I really doubt it suggests doing that.
Maybe people think WM_DESTROY means "please destroy your window" rather than "your window is being destroyed"
In a perfect world it would be nice if windows would see that’s already in the destroy window phase and just ignore the command, but I have the suspicion that such a thing might break things for some applications.
Still it’s interesting to know, it’s something I’ve done inadvertently done before. I didn’t understand it at the time, but this does explains a lot.
: Maybe people think WM_DESTROY means "please
: destroy your window" rather than "your
: window is being destroyed"
That would be silly, because WM_CLOSE means that. ;-)
Well, the old old Windows messages don’t have a distinction between notification messages and window messages.
Looking at WM_DESTROY and WM_CLOSE, and thinking of a "message" being a command, both are telling a window to close or destroy. They’re pretty much synonymous on the surface. Now if WM_DESTROY were named WN_DESTROYING, then that would be a lot easier to see it as "oh, I’m being notified that I’m being destroyed."
I guess I’m just saying it’s just unfortunate all around and not terribly clear.
I agree with Jack. I am sure I have done this myself. With general window messages, it can be hard to tell which are active/request messages and which are notifications.
Jack wrote:
: Looking at WM_DESTROY and WM_CLOSE, and
: thinking of a "message" being a command,
: both are telling a window to close or
: destroy. They’re pretty much synonymous on
: the surface. Now if WM_DESTROY were named
: WN_DESTROYING, then that would be a lot
: easier to see it as "oh, I’m being notified
: that I’m being destroyed."
Well, WM_CLOSE is a command telling you to get rid of that window. WM_DESTROY is a command telling you that the window is being destroyed, kill the resources it owns.
That’s the problem; you can view it as both…
These two surprise me:
> WM_NCDESTROY received – window still exists
> WM_NCDESTROY recursing – window still exists
I thought that WM_NCCREATE came before the window existed (even if it’s not the first message) and WM_NCDESTROY came after the window stopped existing? Sure the recursion is due to the program’s bug, but why was the window still in existence either of these times?
Indeed, the whole thing will be more complex if the WH_CBT hook was installed. Who is the very first message the window received depends upon the hook procedures.
Isn’t any use of IsWindow highly problematic? I mean, just because a window handle is valid now, doesn’t mean (a) it will be valid in a moment’s time, or (b) it refers to the window it used to a while ago.
I’ve seen a lot of WM_DESTROY handling code which goes through considerable amount of trouble to call DestroyWindow() for each child window of the window being destroyed.
This is, of course, unnecessary as Windows will automatically delete the child windows for you. But it looks like Windows recognizes this case and doesn’t call DestroyWindow() on a child window if you have already done so (i.e., I only see one WM_DESTROY message on a child window).
I suppose it’s not terribly clear if you never bother to read the documentation. From MSDN:
"The WM_DESTROY message is sent when a window is being destroyed."
"The WM_CLOSE message is sent as a signal that a window or an application should terminate."
"By default, the DefWindowProc function [for WM_CLOSE] calls the DestroyWindow function to destroy the window."
"The function [DestroyWindow] sends WM_DESTROY and WM_NCDESTROY messages to the window"
"DestroyWindow automatically destroys the associated child or owned windows when it destroys the parent or owner window."
Seems pretty clear to me. The sentence about DestroyWindow sending WM_DESTROY is particularly helpful, as it implies that it would be silly to call DestroyWindow inside a WM_DESTROY.
It seems like hwnd is only valid in the GUI thread that created that hwnd. Any other thread cannot safely use that hwnd. So what is the correct way to send information to a window in a GUI thread that may or may not exist?
WM_DESTROY 和 WM_NCDESTROY 消息之间有什么区别?