{"id":34793,"date":"2005-07-27T10:00:16","date_gmt":"2005-07-27T10:00:16","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2005\/07\/27\/when-the-normal-window-destruction-messages-are-thrown-for-a-loop\/"},"modified":"2005-07-27T10:00:16","modified_gmt":"2005-07-27T10:00:16","slug":"when-the-normal-window-destruction-messages-are-thrown-for-a-loop","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20050727-16\/?p=34793","title":{"rendered":"When the normal window destruction messages are thrown for a loop"},"content":{"rendered":"<p><P>\nLast time,\n<A HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/07\/26\/443384.aspx\">\nI alluded to weirdness that can result in\nthe normal cycle of destruction messages being thrown out of kilter<\/A>.\n<\/P>\n<P>\n<A HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/07\/26\/443384.aspx#443414\">\nCommenter Adrian noted that the <CODE>WM_GETMINMAXINFO<\/CODE> message\narrives before <CODE>WM_NCCREATE<\/CODE> for top-level windows<\/A>.\nThis is indeed unfortunate but (mistake or not)\nit&#8217;s been that way for over a decade and changing it now\nwould introduce serious compatibility risk.\n<\/P>\n<P>\nBut that&#8217;s not the weirdness I had in mind.\n<\/P>\n<P>\nSome time ago I was helping to debug a problem with\na program that was using\n<A HREF=\"http:\/\/msdn.microsoft.com\/library\/en-us\/shellcc\/platform\/commctls\/listview\/listview.asp\">\nthe ListView control<\/A>,\nand the problem was traced to the program subclassing the\nListView control and, through a complicated chain of\nC++ objects, ending up attempting to destroy the ListView\ncontrol while it was already in the process of being destroyed.\n<\/P>\n<P>\nLet&#8217;s take\n<A>\nour new scratch program<\/a>\nand illustrate what happens in a more obvious manner.\n<\/P>\n<PRE>\nclass RootWindow : public Window\n{\npublic:\n <FONT COLOR=\"blue\">RootWindow() : m_cRecurse(0) { }<\/FONT>\n &#8230;\nprivate:\n <FONT COLOR=\"blue\">void CheckWindow(LPCTSTR pszMessage) {\n  OutputDebugString(pszMessage);\n  if (IsWindow(m_hwnd)) {\n   OutputDebugString(TEXT(&#8221; &#8211; window still exists\\r\\n&#8221;));\n  } else {\n   OutputDebugString(TEXT(&#8221; &#8211; window no longer exists\\r\\n&#8221;));\n  }\n }<\/FONT>\nprivate:\n HWND m_hwndChild;\n <FONT COLOR=\"blue\">UINT m_cRecurse;<\/FONT>\n &#8230;\n};<\/p>\n<p>LRESULT RootWindow::HandleMessage(\n                          UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n &#8230;\n  case WM_NCDESTROY:\n   <FONT COLOR=\"blue\">CheckWindow(TEXT(&#8220;WM_NCDESTROY received&#8221;));\n   if (m_cRecurse &lt; 2) {\n    m_cRecurse++;\n    CheckWindow(TEXT(&#8220;WM_NCDESTROY recursing&#8221;));\n    DestroyWindow(m_hwnd);\n    CheckWindow(TEXT(&#8220;WM_NCDESTROY recursion returned&#8221;));\n   }<\/FONT>\n   PostQuitMessage(0);\n   break;<\/p>\n<p>  <FONT COLOR=\"blue\">case WM_DESTROY:\n   CheckWindow(TEXT(&#8220;WM_DESTROY received&#8221;));\n   if (m_cRecurse &lt; 1) {\n    m_cRecurse++;\n    CheckWindow(TEXT(&#8220;WM_DESTROY recursing&#8221;));\n    DestroyWindow(m_hwnd);\n    CheckWindow(TEXT(&#8220;WM_DESTROY recursion returned&#8221;));\n   }\n   break;<\/FONT>\n  &#8230;\n}\n<\/PRE>\n<P>\nWe add some debug traces to make it easier to see what is going on.\nRun the program, then close it, and watch what happens.\n<\/P>\n<PRE>\nWM_DESTROY received &#8211; window still exists\nWM_DESTROY recursing &#8211; window still exists\nWM_DESTROY received &#8211; window still exists\nWM_NCDESTROY received &#8211; window still exists\nWM_NCDESTROY recursing &#8211; window still exists\nWM_DESTROY received &#8211; window still exists\nWM_NCDESTROY received &#8211; window still exists\nWM_NCDESTROY recursion returned &#8211; window no longer exists\nAccess violation &#8211; code c0000005\neax=00267160 ebx=00000000 ecx=00263f40 edx=7c90eb94 esi=00263f40 edi=00000000\neip=0003008f esp=0006f72c ebp=0006f73c iopl=0         nv up ei ng nz na pe cy\ncs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000283\n0003008f ??               ???\n<\/PRE>\n<P>\nYikes!  What happened?\n<\/P>\n<P>\nWhen you clicked the &#8220;X&#8221; button, this started the window destruction\nprocess.\nAs is to be expected,\nthe window received a <CODE>WM_DESTROY<\/CODE> message,\nbut the program responds to this by attempting to destroy the window\n<STRONG>again<\/STRONG>.\nNotice that <CODE>IsWindow<\/CODE> reported that the window still\nexists at this point.\nThis is true: The window does still exist,\nalthough it happens to be in the process of being destroyed.\nIn the original scenario, the code that destroyed the window went\nsomething like\n<\/P>\n<PRE>\nif (IsWindow(hwndToDestroy)) {\n DestroyWindow(hwndToDestroy);\n}\n<\/PRE>\n<P>\nAt any rate, the recursive call to <CODE>DestroyWindow<\/CODE>\ncaused a <STRONG>new<\/STRONG> window destruction cycle to begin,\nnested inside the first one.\nThis generates a new <CODE>WM_DESTROY<\/CODE> message,\nfollowed by a <CODE>WM_NCDESTROY<\/CODE> message.\n(Notice that this window has now received\n<STRONG>two<\/STRONG> <CODE>WM_DESTROY<\/CODE> messages!)\nOur bizarro code then makes yet another\nrecursive call to <CODE>DestroyWindow<\/CODE>,\nwhich starts a <STRONG>third<\/STRONG> window destruction cycle.\nThe window gets its third <CODE>WM_DESTROY<\/CODE> message,\nthen its second <CODE>WM_NCDESTROY<\/CODE> message, at which point\nthe second recursive call to <CODE>DestroyWindow<\/CODE> returns.\nAt this point, the window no longer exists:\n<CODE>DestroyWindow<\/CODE> has destroyed the window.\n<\/P>\n<P>\nAnd that&#8217;s why we crash.\nThe base <CODE>Window<\/CODE> class\nhandles the <CODE>WM_NCDESTROY<\/CODE> message by destroying the\ninstance variables associated with the window.\nTherefore, when the innermost <CODE>DestroyWindow<\/CODE>\nreturns, the instance variables have been thrown away.\nExecution then resumes with the base class&#8217;s\n<CODE>WM_NCDESTROY<\/CODE> handler, which tries to access\nthe instance variables and gets heap garbage,\nand then makes the even worse no-no of freeing memory that\nis already freed, thereby corrupting the heap.\nIt is here that we crash, attempting to call the virtual\ndestructor on an already-destructed object.\n<\/P>\n<P>\nI intentionally chose to use the new scratch program\n(which uses C++ objects) instead of the classic scratch program\n(which uses global variables) to highlight the fact that\nafter the recursive <CODE>DestroyWindow<\/CODE> call,\nall the instance variables are gone and you are operating on\nfreed memory.\n<\/P>\n<P>\nMoral of the story:\nUnderstand your window lifetimes\nand don&#8217;t destroy a window that you know already to be\nin the process of destruction.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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&#8217;s been that way for over a decade and changing it now would [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-34793","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>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&#8217;s been that way for over a decade and changing it now would [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/34793","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=34793"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/34793\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=34793"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=34793"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=34793"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}