{"id":112196,"date":"2026-04-02T07:00:00","date_gmt":"2026-04-02T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112196"},"modified":"2026-04-02T07:35:55","modified_gmt":"2026-04-02T14:35:55","slug":"20260402-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260402-00\/?p=112196","title":{"rendered":"Why doesn&#8217;t the system let you declare your own messages to have the same semantics as <CODE>WM_<WBR>COPY&shy;DATA<\/CODE>?"},"content":{"rendered":"<p>In a comment on my <a title=\"I can use WM_COPYDATA to send a block of data to another window, but how does it send data back?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251114-00\/?p=111792\"> discussion on how to return results back from the <code>WM_<wbr \/>COPY\u00adDATA<\/code> message<\/a>, Jan Ringo\u0161 observed that it felt wasteful that there was this entire infrastructure for copying blocks of memory via a window message, yet only one message uses it! &#8220;<a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251114-00\/?p=111792&amp;commentid=143446#comment-143446\">I always thought something like EnableWindowMessageDataCopy (HWND, UINT, .)<\/a> after RegisterWindowMessage and ChangeWindowMessageFilterEx to get application&#8217;s own private WM_COPYDATA would be a little more secure and convenient, should the programmer didn&#8217;t wish to bother with creating shared memory.&#8221;<\/p>\n<p>The infrastructure for copying blocks of memory via a window message is used by far more than just one message! The <code>WM_<wbr \/>SET\u00adTEXT<\/code> and <code>WM_<wbr \/>GET\u00adTEXT<\/code> message use it for passing string buffers, the <code>WM_<wbr \/>HELP<\/code> message uses it for passing the <code>HELPINFO<\/code> structure, the <code>WM_<wbr \/>MDICREATE<\/code> message uses it for passing the <code>MDICREATSTRUCT<\/code> structure, and plenty more where those came from. The infrastructure for copying blocks of memory had already existed; it wasn&#8217;t created just for the <code>WM_<wbr \/>COPY\u00adDATA<\/code> message. adding <code>WM_<wbr \/>COPY\u00adDATA<\/code> support was just adding a few lines of code to the common function whose job is to prepare messages to be sent between processes (including copying memory between processes).<\/p>\n<p>Suppose there were a way for a program to declare that one of its custom messages should have (say) its <code>lParam<\/code> be a pointer to data and its <code>wParam<\/code> be the size of the data. That could be misleading because the only behavior would be copying the memory block and not the data inside it. For example, if the structure contained pointers, the pointers would just be copied as raw values, rather than adding the pointed-to-data to the memory block and adjusting the pointers to point to the copy. It also doesn&#8217;t handle the case of sending the message between programs with different pointer or handle sizes, say between a 32-bit program and a 64-bit program.\u00b9 If you need to copy data structures that consists of anything more than scalars (or aggregates of scalars), you&#8217;ll have to do your own marshaling to convert your source data structure into a transfer buffer. In practice, this means that sending the message directly with an as-is buffer is unlikely to be the common case; some type of conversion would have to be made anyway.<\/p>\n<p>Furthermore, the <code>WM_<wbr \/>COPY\u00adDATA<\/code> already knew that you wanted to do this, because it left room for it in the <code>COPY\u00adDATA\u00adSTRUCT<\/code>:<\/p>\n<pre>typedef struct tagCOPYDATASTRUCT {\r\n  ULONG_PTR dwData; \/\/ \u2190 here\r\n  DWORD     cbData;\r\n  PVOID     lpData;\r\n} COPYDATASTRUCT, *PCOPYDATASTRUCT;\r\n<\/pre>\n<p>In addition to describing the memory buffer, there is this extra guy called <code>dwData<\/code>. You can put your &#8220;message number&#8221; in there, allowing you to multiplex multiple &#8220;messages&#8221; into a single <code>WM_<wbr \/>COPY\u00adDATA<\/code> message.\u00b2<\/p>\n<p>You don&#8217;t need <code>Enable\u00adWindow\u00adMessage\u00adData\u00adCopy<\/code> because you already have it at home. The window manager is more concerned with enabling things that weren&#8217;t possible before, rather than making it easier to do things that are already possible. For that, you can use a helper library.<\/p>\n<p><b>Bonus chatter<\/b>: In addition to adding complexity to the window manager implementation, allowing programs to customize how messages are marshaled between processes would also make it harder to explain how inter-process marshaling works. Instead of the simple rule &#8220;The system marshals messages in the system range, but not messages in the user-defined range,&#8221; it would be a much more ambiguous rule: &#8220;The system marshals messages in the system range, but not messages in the user-defined range, unless those messages have been customized by a call to <code>Enable\u00adWindow\u00adMessage\u00adData\u00adCopy<\/code>, in which case they marshal by this alternate set of rules.&#8221; So now when you look at a message, you can&#8217;t tell how it marshals. You&#8217;d have to go back to the documentation for the message and hope the person who wrote the documentation remembered to go back and add a section to each page to say whether it follows custom marshaling.<\/p>\n<p>\u00b9 Or between a 16-bit program and a 32-bit program, which was the more common case back in the days when <code>WM_<wbr \/>COPY\u00adDATA<\/code> was designed. In 16-bit code, an <code>int<\/code> is a 16-bit integer, whereas it&#8217;s a 32-bit value in 32-bit code.<\/p>\n<p>\u00b2 If the <code>dwData<\/code> was intended to be a message number, why is it pointer-sized? For the same reason <a title=\"Why are timer IDs and dialog control IDs 64-bit values on 64-bit Windows? Did you really expect people to create more than 4 billion timers or dialog controls?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191010-00\/?p=102978\"> timer IDs and dialog control IDs are 64-bit values<\/a>: &#8220;Pointers are like weeds. Anywhere it&#8217;s possible to fit a pointer, a pointer will try to squeeze in there.&#8221; In this case, people were putting handles (which are pointer-sized) in the <code>dwData<\/code>, so we had to make it big enough to hold a handle.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Tempting but misleading.<\/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-112196","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Tempting but misleading.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112196","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=112196"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112196\/revisions"}],"predecessor-version":[{"id":112197,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112196\/revisions\/112197"}],"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=112196"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112196"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112196"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}