{"id":107612,"date":"2022-12-23T07:00:00","date_gmt":"2022-12-23T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107612"},"modified":"2022-12-15T16:03:00","modified_gmt":"2022-12-16T00:03:00","slug":"20221223-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20221223-00\/?p=107612","title":{"rendered":"The case of the recursively hung <CODE>WM_DRAW&shy;CLIPBOARD<\/CODE> message"},"content":{"rendered":"<p>An application hang report showed that the application was stuck in this stack:<\/p>\n<pre>win32u!ZwUserMessageCall+0x14\r\nuser32!SendMessageWorker+0x823\r\nuser32!SendMessageW+0xda\r\ncontoso!CContosoWindow::WndProc+0xa5d\r\nuser32!UserCallWinProcCheckWow+0x2f8\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserMessageCall+0x14\r\nuser32!SendMessageWorker+0x823\r\nuser32!SendMessageW+0xda\r\ncontoso!CContosoWindow::WndProc+0xa5d\r\nuser32!UserCallWinProcCheckWow+0x2f8\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserMessageCall+0x14\r\nuser32!SendMessageWorker+0x823\r\nuser32!SendMessageW+0xda\r\ncontoso!CContosoWindow::WndProc+0xa5d\r\nuser32!UserCallWinProcCheckWow+0x2f8\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserMessageCall+0x14\r\nuser32!SendMessageWorker+0x823\r\nuser32!SendMessageW+0xda\r\ncontoso!CContosoWindow::WndProc+0xa5d\r\nuser32!UserCallWinProcCheckWow+0x2f8\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserMessageCall+0x14\r\nuser32!SendMessageWorker+0x823\r\nuser32!SendMessageW+0xda\r\ncontoso!CContosoWindow::WndProc+0xa5d\r\nuser32!UserCallWinProcCheckWow+0x2f8\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserMessageCall+0x14\r\nuser32!SendMessageWorker+0x823\r\nuser32!SendMessageW+0xda\r\ncontoso!CContosoWindow::WndProc+0xa5d\r\nuser32!UserCallWinProcCheckWow+0x2f8\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserMessageCall+0x14\r\nuser32!SendMessageWorker+0x823\r\nuser32!SendMessageW+0xda\r\ncontoso!CContosoWindow::WndProc+0xa5d\r\nuser32!UserCallWinProcCheckWow+0x2f8\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserMessageCall+0x14\r\nuser32!SendMessageWorker+0x823\r\nuser32!SendMessageW+0xda\r\ncontoso!CContosoWindow::WndProc+0xa5d\r\nuser32!UserCallWinProcCheckWow+0x2f8\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserMessageCall+0x14\r\nuser32!SendMessageWorker+0x823\r\nuser32!SendMessageW+0xda\r\ncontoso!CContosoWindow::WndProc+0xa5d\r\nuser32!UserCallWinProcCheckWow+0x2f8\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserMessageCall+0x14\r\nuser32!SendMessageWorker+0x823\r\nuser32!SendMessageW+0xda\r\ncontoso!CContosoWindow::WndProc+0xa5d\r\nuser32!UserCallWinProcCheckWow+0x2f8\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nuser32!_InternalCallWinProc+0x2a\r\nuser32!InternalCallWinProc+0x1b\r\nuser32!DispatchClientMessage+0xea\r\nuser32!__fnDWORD+0x3f\r\nntdll!KiUserCallbackDispatcher+0x4c\r\nwin32u!NtUserGetMessage+0xc\r\nuser32!GetMessageW+0x30\r\ncontoso!WindowThreadProc+0x9b\r\nkernel32!BaseThreadInitThunk+0x14\r\nntdll!RtlUserThreadStart+0x21\r\n<\/pre>\n<p>Inspecting the local variables at each recursive call shows that the message is always <code>WM_<wbr \/>DRAW\u00adCLIPBOARD<\/code>. The Contoso window receives the <code>WM_<wbr \/>DRAW\u00adCLIPBOARD<\/code> message, does its work, and then forwards the message to the next clipboard viewer window, just like the book says. While waiting for that window to respond, another <code>WM_<wbr \/>DRAW\u00adCLIPBOARD<\/code> message arrives, and the cycle repeats.<\/p>\n<p>The clipboard viewer chain is a linked list of windows that have all subscribed to clipboard notifications. This linked list is managed cooperatively: When you add yourself to the chain, you are given the handle of the previous head of the chain. And when you finish dealing with a clipboard notification, you forward the notification to the next window in the chain. That way, all the windows in the chain eventually learn about the clipboard.<\/p>\n<p>The clipboard viewer chain was developed back in the days of 16-bit Windows, when all programs were cooperatively multi-tasked and generally were trusted to behave properly. The clipboard viewer chain used the same trick that <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20060809-18\/?p=30183\"> window hooks used<\/a> to save space: It externalized the cost.<\/p>\n<p>Here&#8217;s a sketch of how the clipboard viewer chain worked in 16-bit Windows:<\/p>\n<pre>HWND hwndClipboardViewer;\r\n\r\nHWND SetClipboardViewer(HWND hwndNewViewer)\r\n{\r\n  HWND hwndOldViewer = hwndClipboardViewer;\r\n  hwndClipboardViewer = hwndNewViewer;\r\n  return hwndOldViewer;\r\n}\r\n\r\nHWND GetClipboardViewer()\r\n{\r\n  return hwndClipboardViewer;\r\n}\r\n\r\nHWND ChangeClipboardChain(HWND hwndRemove, HWND hwndNewNext)\r\n{\r\n  if (hwndClipboardViewer == hwndRemove) {\r\n    hwndClipboardViewer = hwndNewNext;\r\n  } else {\r\n    SendMessage(hwndClipboardViewer, WM_CHANGECBCHAIN,\r\n        (WPARAM)hwndRemove, (LPARAM)hwndNewNext);\r\n  }\r\n}\r\n\r\nvoid NotifyClipboardViewers()\r\n{\r\n  if (hwndClipboardViewer) {\r\n    SendMessage(hwndClipboardViewer, WM_DRAWCLIPBOARD, 0, 0);\r\n  }\r\n}\r\n<\/pre>\n<p>And that&#8217;s it! The entire clipboard viewer feature in 30 lines of code.<\/p>\n<p>Okay, so back to our customer&#8217;s problem.<\/p>\n<p>The window registered itself as a clipboard viewer, and the clipboard contents changed, causing it to receive a <code>WM_<wbr \/>DRAW\u00adCLIPBOARD<\/code> message. The window dealt with the clipboard change, and then dutifully called <code>Send\u00adMessage<\/code> to forward the <code>WM_<wbr \/>DRAW\u00adCLIPBOARD<\/code> message down the chain. Every window in the chain deals with the message, and then calls <code>Send\u00adMessage<\/code>.<\/p>\n<p>What happened here is that some window in the chain is hung, and that causes all the other windows in the chain to hang, since they are all blocked on each other via <code>Send\u00adMessage<\/code>:<\/p>\n<table class=\"cp3\" style=\"text-align: center;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td style=\"border: solid 1px gray;\">Window 1<\/td>\n<\/tr>\n<tr>\n<td>\u2193<\/td>\n<td>SendMessage<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">Window 2<\/td>\n<\/tr>\n<tr>\n<td>\u2193<\/td>\n<td>SendMessage<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">Window 3<\/td>\n<\/tr>\n<tr>\n<td>\u2193<\/td>\n<td>SendMessage<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">Window 4<\/td>\n<td>hung<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In order for Window\u00a01&#8217;s <code>Send\u00adMessage<\/code> to complete, Window\u00a02 needs to return. But Window\u00a02 is stuck in a <code>Send\u00adMessage<\/code> to Window\u00a03, which is in turn stock in a <code>Send\u00adMessage<\/code> to Window\u00a04, which is hung. That one hung window has caused a chain of windows to stop responding.<\/p>\n<p>The Contoso window got caught in the chain of windows that are all waiting for that other hung window to process the <code>WM_<wbr \/>DRAW\u00adCLIPBOARD<\/code> message.<\/p>\n<p>So what can Contoso do about this?<\/p>\n<p>The best solution is to leave the game. Instead of using the old and busted clipboard viewer chain, use the new hotness <code>Add\u00adClipboard\u00adFormat\u00adListener<\/code> function to <a title=\"The clipboard viewer linked list is no longer the responsibility of applications to maintain, unless they want to\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20110919-00\/?p=9613\"> register to be notified when the clipboard contents change<\/a> and escape the clipboard viewer chain.<\/p>\n<p>Fortunately, converting from a clipboard viewer to a clipboard format listener is fairly simple and even involves deleting some code, so that&#8217;s a nice bonus.<\/p>\n<ul>\n<li>Change <code>Set\u00adClipboardViewer<\/code> to <code>Add\u00adClipboard\u00adFormat\u00adListener<\/code>.<\/li>\n<li>Delete the variable that held the previous clipboard viewer.<\/li>\n<li>Delete the code that handled the <code>WM_<wbr \/>CHANGE\u00adCB\u00adCHAIN<\/code> message.<\/li>\n<li>Change <code>case WM_DRAWCLIPBOARD<\/code> to <code>case WM_CLIPBOARDUPDATE<\/code>.<\/li>\n<li>Delete the <code>Send\u00adMessage(hwndNextViewer, WM_DRAWCLIPBOARD, wParam, lParam)<\/code>.<\/li>\n<li>Change <code>Change\u00adClipboard\u00adChain<\/code> to <code>Remove\u00adClipboard\u00adFormat\u00adListener<\/code>.<\/li>\n<\/ul>\n<p>If for some reason you really want to be a clipboard viewer, you can at least switch to using <code>Send\u00adNotify\u00adMessage<\/code> to forward the <code>WM_<wbr \/>DRAW\u00adCLIPBOARD<\/code> message to the next window in the chain. The <code>Send\u00adNotify\u00adMessage<\/code> function is like <code>Send\u00adMessage<\/code> except that it doesn&#8217;t want for the recipient to return. It&#8217;s a fire-and-forget <code>Send\u00adMessage<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Understanding why it&#8217;s happening and how you can avoid it.<\/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-107612","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Understanding why it&#8217;s happening and how you can avoid it.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107612","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=107612"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107612\/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=107612"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107612"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107612"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}