You know the answer: Window destruction


The following request for assistance came in from a customer, and given what you know about window destruction, you should eventually be able to figure it out yourself once all the pieces are in place, though it takes some time for all the clues to be revealed.

We are hitting this exception in our program. This is urgent; please give it priority attention.

0006f6ac kernel32!InterlockedCompareExchange+0xc
0006f6ec comctl32!CImageListBase::IsValid+0x2a
0006f6fc comctl32!HIMAGELIST_QueryInterface+0x2c
0006f714 comctl32!ImageList_GetBkColor+0x1b
0006f724 comctl32!TV_HasTransparentImage+0x1c
0006f744 comctl32!TV_SelectItem+0x1a
0006f7d4 comctl32!TV_DeleteItemRecurse+0x12a
0006f85c comctl32!TV_DeleteItemRecurse+0x58
0006f87c comctl32!TV_DeleteItem+0x8c
0006f89c comctl32!TV_DestroyTree+0x90
0006f900 comctl32!TV_WndProc+0x2e7
0006f92c USER32!InternalCallWinProc+0x23
0006f9a4 USER32!UserCallWinProcCheckWow+0x14b
0006fa00 USER32!DispatchClientMessage+0xda
0006fa28 USER32!__fnDWORD+0x24
0006fa54 ntdll!KiUserCallbackDispatcher+0x2e
0006fa58 USER32!NtUserDestroyWindow+0xc
0006faac comctl32!_RealPropertySheet+0x307
0006fac0 comctl32!_PropertySheet+0x45
0006fad0 comctl32!PropertySheetW+0xf
0006fbc4 abc!Wizard::ModalExecute+0x17c
0006fc50 abc!RunWizard+0x564
0006fcac abc!Start+0x185
0006fd70 abc!ABCEntryW+0x2b9
0006ff5c abc!wmain+0x7db
0006ffa0 abc!__tmainCRTStartup+0x10f
0006ffac kernel32!BaseThreadInitThunk+0xe
0006ffec ntdll!_RtlUserThreadStart+0x23

The proximate problem is that the treeview control is trying to use an imagelist that is no longer valid, probably because it has already been destroyed. If you look at the stack, you can see that the treeview control is being destroyed. You might infer this from the function named TV_DestroyTree, or if you look at the parameters, which I removed from the stack trace for brevity, you would see that the message is WM_DESTROY.

The next step in unwinding the problem is figuring out who destroyed the imagelist while it was still in use. The customer shared their source code, and a little bit of spelunking revealed that it was this function which destroyed the imagelist:

void
XYZPage::OnDestroy()
{
   if (m_hImageList)
   {
      ImageList_RemoveAll(m_hImageList);
      ImageList_Destroy(m_hImageList);
   }
}

Now all the clues to the puzzle have been laid out on the table. Use your fantastic powers of deduction to see where the customer went wrong. To refresh your memory, you might want to read this old blog entry.

(And now that you've seen and understood this problem, in the future you can jump from the stack trace directly to the conclusion, thereby exhibiting your psychic debugging powers.)

Comments (18)
  1. BryanK says:

    The tree-view was a child window.  The parent window got their WM_DESTROY (which caused MFC (or whatever) to call XYZPage::OnDestroy), then the window manager sent a WM_DESTROY to the tree-view window.  The tree-view window code tried to refer to the image-list.

    I can think of a couple possible fixes: first, change OnDestroy to OnNcDestroy (assuming the framework provides this function).  Otherwise, before the image-list is destroyed, remove the tree-view’s reference to it (if possible); that way the tree-view won’t be able to refer to the list after it’s gone.  However, this may screw up other things; it’d be better to use OnNcDestroy (AKA the WM_NCDESTROY message).

  2. I’m proud of myself – this is one of the rare occasions when my psychic debugging was up to the task of solving one of the problems on this blog. As soon as I got to the point that the imagelist was being accessed in the treeview’s destroy function, I knew it was going to be an issue of freeing resources at the beginning of the cleanup process, before the children have been freed, rather than at the end.

    I had to read the referenced prior entry to understand what the relevance of messages going to windows was, but still, that’s not bad for someone who lives in C# ASP.NET apps and has never written a windows C/C++ program in his life, I think…

  3. harmony7 says:

    As BryanK has pointed out above, the window manager is sending WM_DESTROY to the child window, AFTER the parent window’s OnDestroy handler has already completed, since the child window didn’t do it itself.

    I would handle this by modifying the OnDestroy handler (of the parent window) by sending WM_DESTROY to the TreeView window myself, before the lines that free the image list.  (The call would be similar to m_hWndTreeView.DestroyWindow(), if using MFC)

    However, with this approach I am hoping the Image List and TreeView are both initialized in OnCreate() (in that order).  At least, that is how I would put it together.

    WM_NCDESTROY would work because of the order that the message is sent, but I sort of feel weird putting code in there if I can avoid it.  I would do that if I am initializing the image list in WM_NCCREATE.  But doesn’t that feel wacky?

  4. Dataland says:

    If I had to guess, I see a problem with these two:

         ImageList_RemoveAll(m_hImageList);

         ImageList_Destroy(m_hImageList);

    Guessing… ImageList_RemoveAll just removed the images from the imageList.  Then ImageList_Destroy is trying to do destruction and inadvertently (or due to a poor design coupled with over or under engineering), tries to reference the ImageList that was just freed: ImageList_GetBkColor.  Why are they calling this in the destructor?

    I do have to guess, because you tell me WM_Destroy was called and… I cannot see if we are in ImageList_Destroy, which I guess we are.  Are we?

  5. BryanK says:

    I’m not sure that it’s a good idea to send a WM_DESTROY on your own.  Won’t the window manager send another one anyway?  (Of course by that time maybe the tree-view has released its reference to the image list anyway, so maybe that would work.  Still seems a bit odd to be sending that type of message to child windows manually, though.)

    I’d say that initializing the image-list in the OnNcCreate handler probably makes more sense than manufacturing fake messages, but that’s just me.  There’s already a place to hook into the destruction notification after the child windows have done their thing; why not just use it?  :-)

  6. meh says:

    I love these sorts of postings. :)

  7. ghbyrkit says:

    If you free a resource used by a child, then remove the reference from the child, before its WM_DESTROY message is processed.  If this doesn’t work, then you must move the cleanup of the resource (image list) to the WM_NCDESTROY handler, where as the article Raymond refers to puts it: "The WM_NCDESTROY is the last message your window will receive (in the absence of weirdness), and it is therefore the best place to do "final cleanup". This is why our new scratch program waits until WM_NCDESTROY to destroy its instance variables."

    So the bet is that the second action, moving it to the WM_NCDESTROY handler, is the choice.

  8. Comctl says:

    If you destroy the imagelist, you need to call TVM_SETIMAGELIST so that it doesn’t have the reference to it in its destruction handler. Then you don’t need to move the ImageList_Destroy call.

  9. Dean Harding says:

    I liked the "This is urgent; please give it priority attention." As if customers call PSS with issues that are not urgent…

  10. harmony7 says:

    With regard to the WM_NCDESTROY message, it’s probably just me, but I’ve never ever had to handle this message in all 8+ years of my Windows programming career.

    If we’re using MFC, we’d generally put an instance of CWnd (or derived class) as a class member of our parent window.  In the parent window’s OnCreate, we’d call the CreateEx member of the child window, and in the OnDestroy, we’d call DestroyWindow.  While it’s true that the child window would be destroyed automatically when the parent window is destroyed, that sounds to me along the same lines as not closing a file or unregistering a window class because that stuff all happens when a process shuts down.

    If you’re not using MFC, I’d be creating the child window (using CreateWindowEx) in the parent window’s WM_CREATE handler.  So wouldn’t it just be natural to put a DestroyWindow in the WM_DESTROY handler?  Why WM_NCDESTROY?  It doesn’t make semantic sense to me.

  11. AndreiM says:

    Now I know how items are released from a tree control.

    I think the code works like this:

    1. WM_DESTROY is received
    2. The ImageList is cleaned-up but not unset from the tree.

    3. Next, the Window Manager tries to destroy the TreeControl, and first will remove all items.

    4. After the first item is removed, will set another item to be active, which will cause a repaint, and the image list (now invalid) is used, therefore … crash.

    The solution would be either to cleanup on WM_NCDESTROY (requires more knowledge) or unset the image-list for the control – for beginners.

  12. harmony7 says:

    Sorry, I noticed I messed up in my earlier post when I said that one should send the WM_DESTROY message to a child window directly.  I meant to say that "one should destroy the child window directly" (thereby causing WM_DESTROY to be sent to it).

    Everyone who is saying that the ImageList should be cleaned up during the handling of WM_NCDESTROY is correct in that it would work.  I’m not disagreeing with that.  I’m just trying to say that, although it is true that WM_DESTROY is sent parent-first and WM_NCDESTROY is sent child-first, I don’t believe that the purpose the two exist is just to have a separate destruction notification mechanism for each direction, but to handle two meaningfully separate events (the destruction of the window itself and the destruction of the window’s non-client area).

  13. ghbyrkit says:

    The point is: if you do NOT use the WM_NCDESTROY handling pattern, you will forever be finding problems such as this written by those who do not understand that the parent’s data items shared with its children are not done with until the parent receives the WM_NCDESTROY message and processes it.  Simply calling Destroy on a child to ‘post a WM_DESTROY’ message doesn’t get that message processed by the child at that time.  Unhooking the data item from the child during WM_DESTROY of the parent only works if you track what you’ve added and do things carefully.  So waiting until WM_NCDESTROY is so much simpler and more cerain to succeed…

  14. JM says:

    Well, of course you don’t *need* to handle WM_NCDESTROY if you use a resource usage and freeing pattern that works with WM_DESTROY. The point is that, if you have a choice from the beginning, using WM_NCDESTROY for cleanup is more intuitive, because WM_NCDESTROY is sent in the "natural" order for destruction (children first, then parent), which is the opposite order of construction. Using WM_NCDESTROY for cleanup means that, when you receive WM_DESTROY, you can assume all your children and their resources are still valid. Conversely, when you process WM_NCDESTROY, you know all child windows have been destroyed and their resources have been freed.

    Putting a DestroyWindow() in the parent’s WM_DESTROY handler is just clumsy, because WM_DESTROY is sent to child windows before it’s sent to the parent window. All you achieve with that is that the child window will have to be careful not to cleanup twice, and it certainly doesn’t make shared resource cleanup any easier. This is exactly why it’s not the most intuitive way for doing resource cleanup, and it’s exactly what went wrong here.

  15. JM says:

    Never mind about the brain farting in my last post… Of course WM_DESTROY is sent to the *parent* first before it’s sent to children…

  16. Igor Levicki says:

    …and a little bit of spelunking revealed…

    -Surely, you’ve heard of me. I am Raymond Chen, the famous spelunker.

    -Me too! It’s such a pleasure to meet a fellow ‘lunker’!

  17. hexatron says:

    The guy who decided WM_CREATE on the main window should happen after the child windows–are his children also older than he is–is he the same guy who hung his pictures on the wall before he built the wall–the guy who thot backslash was the perfect path-delimiter? Is his name really Bass Ackwards? Izze? Wazee?

  18. hexatron says:

    Here are the messages sent to a WS_OVERLAPPED window from when CreateWindow is called until CreeateWindow returns:

    WM_GETMINMAXINFO 0x0 0x12fdb8

    WM_NCCREATE 0x0 0x12fdb0

    WM_NCCALCSIZE 0x0 0x12fdd8

    WM_CREATE 0x0 0x12fd60

    WM_SETTEXT 0x0 0x12fc38

    and on destruction only

    WM_DESTROY

    WM_NCDESTROY

    A dialog was a little different–

    calling DialogBox() generated (in order) (about is the dialog box)

    main WM_CANCELMODE 0x0 0x0

    main WM_KILLFOCUS 0x0 0x0

    main WM_ENABLE 0x0 0x0

    about WM_SETFONT 0xd0a07bc 0x0

    about WM_INITDIALOG 0x8302ee 0x0

    main WM_NCACTIVATE 0x0 0x5e01ea

    main WM_GETTEXT 0xff 0x12f204

    main WM_ACTIVATE 0x0 0x5e01ea

    about WM_WINDOWPOSCHANGING 0x0 0x12fc14

    main WM_WINDOWPOSCHANGING 0x0 0x12fc14

    main WM_NCPAINT 0x1 0x0

    main WM_GETTEXT 0xff 0x12f204

    main WM_ERASEBKGND 0xb2010922 0x0

    main WM_WINDOWPOSCHANGED 0x0 0x12fc14

    about WM_NCACTIVATE 0x1 0x5e01ac

    about WM_ACTIVATE 0x1 0x5e01ac

    about WM_USER 0x0 0x0

    about WM_CHANGEUISTATE 0x3 0x0

    main WM_PAINT 0x0 0x0

    about WM_SHOWWINDOW 0x1 0x0

    about WM_WINDOWPOSCHANGING 0x0 0x12fc98

    about WM_NCPAINT 0x1 0x0

    about WM_GETTEXT 0xff 0x12f394

    about WM_ERASEBKGND 0x3010830 0x0

    about WM_CTLCOLORDLG 0x3010830 0x5e01ea

    about WM_WINDOWPOSCHANGED 0x0 0x12fc98

    about WM_SIZE 0x0 0x8300d8

    about WM_MOVE 0x0 0x12400ee

    about WM_PAINT 0x0 0x0

    (it goes on, but painting the dialog seems like a good place to stop)

    which is lots of messages, but there is no WM_NCCREATE sent to the dialog. However, when the dialog box is destroyed, we do get

    about WM_DESTROY

    about WM_NCDESTROY

    So much for fearful symmetry!

    This only took a few minutes and was pretty educational, though I doubt if I really needed to know about the WM_CANCELMODE message.

    [This is discussed in my book, but it’s obvious if you think about it. (How can something that is established during initialization happen before the initialization itself?) -Raymond]

Comments are closed.

Skip to main content