Using WM_COPYDATA to marshal message parameters since the window manager otherwise doesn’t know how

Miral asks for the recommended way of passing messages across processes if they require custom marshaling.

There is no one recommended way of doing the custom marshaling, although some are hackier than others.

Probably the most architecturally beautiful way of doing it is to use a mechanism that does perform automatic marshaling, like COM and MIDL. Okay, it's not actually automatic, but it does allow you just give MIDL your structures and some information about how they should be interpreted, and the MIDL compiler autogenerates the marshaler. You can then pass the data back and forth by simply invoking COM methods and letting COM do the work.

Architecturally beautiful often turns into forcing me to learn more than I really wanted to learn, so here's a more self-contained approach: Take advantage of the WM_COPY­DATA message. This is sort of the poor-man's marshaler. All it knows how to marshal is a blob of bytes. It's your responsibility to take what you want to marshal and serialize it into a blob of bytes. WM_COPY­DATA will get the bytes to the other side, and then the recipient needs to deserialize the blob of bytes back into your data. But at least WM_COPY­DATA does the tricky bit of getting the bytes from one side to the other.

Let's start with our scratch program and have it transfer data to another copy of itself. Make the following changes:

#include <strsafe.h>

HWND g_hwndOther;


void OnWindowPosChanged(HWND hwnd, LPWINDOWPOS pwp)
 if (g_hwndOther) {
  cds.cbData = sizeof(WINDOWPOS);
  cds.lpData = pwp;
  SendMessage(g_hwndOther, WM_COPYDATA,

void OnCopyData(HWND hwnd, HWND hwndFrom, PCOPYDATASTRUCT pcds)
 switch (pcds->dwData) {
  if (pcds->cbData == sizeof(WINDOWPOS)) {
   LPWINDOWPOS pwp = static_cast<LPWINDOWPOS>(pcds->lpData);
   TCHAR szMessage[256];
   StringCchPrintf(szMessage, 256,
    TEXT("From window %p: x=%d, y=%d, cx=%d, cy=%d, flags=%s %s"),
    hwndFrom, pwp->x, pwp->y, pwp->cx, pwp->cy,
    (pwp->flags & SWP_NOMOVE) ? TEXT("nomove") : TEXT("move"),
    (pwp->flags & SWP_NOSIZE) ? TEXT("nosize") : TEXT("size"));
   SetWindowText(hwnd, szMessage);

// WndProc

    HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGED, OnWindowPosChanged);
    HANDLE_MSG(hwnd, WM_COPYDATA, OnCopyData);

// WinMain
    // If there is another window called "Scratch", then it becomes
    // our recipient.
    g_hwndOther = FindWindow(TEXT("Scratch"), TEXT("Scratch"));

    hwnd = CreateWindow(
        "Scratch",                      /* Class Name */
        g_hwndOther ? TEXT("Sender") : TEXT("Scratch"),
        WS_OVERLAPPEDWINDOW,            /* Style */
        CW_USEDEFAULT, CW_USEDEFAULT,   /* Position */
        CW_USEDEFAULT, CW_USEDEFAULT,   /* Size */
        NULL,                           /* Parent */
        NULL,                           /* No menu */
        hinst,                          /* Instance */
        0);                             /* No special parameters */

Just to make it easier to tell the two windows apart, I call the one sending the message "Sender". (Note that my method for finding the other window is pretty rudimentary, because that's not the point of the example.)

Whenever the sender window receives a WM_WINDOW­POS­CHANGED message, it sends a copy of the WINDOW­POS structure to the recipient, which then displays it in its own title bar. Things to observe:

  • The value you put into dwData can be anything you like. It's just another DWORD of data. Traditionally, it's used like a "message number", used to communicate what type of data is being sent. In our case, we choose 42 to mean "The lpData points to a WINDOW­POS structure."

  • The cbData is the number of bytes you want to send, and lpData points to the buffer. In our case, the number of bytes is always the same, but variable-sized data is also fine.

  • The lpData can point anywhere, as long as the memory is valid for the lifetime of the Send­Message call. In this case, I just point it at the data given to me by the window manager. Of course, if you allocated memory to put into lpData, then the responsibility for freeing it also belongs to you.

  • For safety's sake, I validate that when I get a CDS­CODE_WINDOW­POS request, the associated data really is the size of a WINDOW­POS structure. This helps protect against a rogue caller who tries to crash the application by sending a CDS­CODE_WINDOW­POS with a size less than sizeof(WINDOW­POS), thereby triggering a buffer overflow. (Exercise: Under what other conditions can the size be incorrect? How would you fix that?)

  • The WM_COPY­DATA copies data in only one direction. It does not provide a way to pass information back to the sender. (Exercise: How would you pass information back?)

  • The hwndFrom parameter is a courtesy parameter, like dwData. There is currently no attempt to verify that the window really is that of the sender. (In practice, all that could really be verified is that the window belongs to the thread that is doing the sending, but right now, not even that level of validation is performed.)

The WM_COPY­DATA message is suitable for small-to-medium-sized amounts of memory. Though if the amount of memory is so small that it fits into a WPARAM and LPARAM, then even WM_COPY­DATA is overkill.

If you're going to be passing large chunks of memory, then you may want to consider using a shared memory handle instead. The shared memory handle also has the benefit of being shared, which means that the recipient can modify the shared memory block, and the sender can see the changes. (Yes, this is one answer to the second exercise, but see if you can find another answer that tays within the spirit of the exercise.)

Comments (17)
  1. Anonymous says:

    The size of WINDOWPOS would be incorrect if one process was 32-bit and the other was 64-bit.  I would this this by sending a different structure that only contained the x, y, cx, cy and flags fields.

    If I wanted to pass information back then I'd probably send another window message back to the first window (which you know from the wParam of the WM_COPYDATA message), although you would have to use a PostMessage instead of SendMessage to avoid deadlocks.

    [No deadlock here, because a thread can process inbound sent messages while waiting for an outgoing sent message to complete. -Raymond]
  2. WndSks says:

    You can of course send a WM_COPYDATA back to the "client" if you need to return data but if you need to send a lot of these messages it might be better to use SHAllocShared or some other shared memory map implementation…

  3. "No deadlock here, because a thread can process inbound sent messages while waiting for an outgoing sent message to complete. -Raymond"

    I don't get it.  If both threads/processes are using SendMessage to call each other, wouldn't they be in a state of deadlock?

    1.  Process A calls SendMessage to send to Process B, and waits for process B's window procedure to return.

    2.  Process B's window procedure is now called, and calls SendMessage to send to process A.  Now process B waits for process A to process that message.

    3.  Process A will never process message sent by process B because it's still waiting for B's window procedure to return.

    There are other message functions that seem much more appropriate in this situation.

    [In step 3, process A is woken up "because a thread can process inbound sent messages while waiting for an outgoing sent message to complete." -Raymond]
  4. Anonymous says:

    WM_COPYDATA is also fast.

    I was writing a logger process and was surprised that it would log thousands of log string

    per second — each send with WM_COPYDATA from another process.


  5. Anonymous says:

    @AsmGuru62: Yeah, copying a contiguous block of data from one memory location to another is very fast, especially if you know what CPU you're using and you can use that to tune the code to use the SIMD registers.

  6. Anonymous says:

    Does anybody know when WM_COPYDATA was created? Obviously it had to exist for Win32 to work (because different windows could have different address spaces), but it's not clear that there was a use for it in 16-bit Windows.

  7. Anonymous says:

    @Gabe: I remember noticing it in the pre-release docs for Win32 and I remember thinking it was new. As you say, Win16 doesn't need it and Win32 almost certainly does.

  8. Anonymous says:

    Well that's a lot cleaner than XxxxProcessmemory. And it handles 32/64 bit correctly if your structs are defined sanely* (system structs often aren't).

    *local definition is suitable for use in binary file formats.

  9. Anonymous says:

    @Gabe: Of course, there's nothing to stop you from sending a 0x004A message in 16-bit Windows and passing an hWnd in wParam and a COPYDATASTRUCT in lParam ;-)

  10. Anonymous says:

    Hooray for WM_COPYDATA, I use it since..forever and never failed me. I disagree Ray, is not "poor-man's marshaler" but is the smart man's marshaler, KISS!

  11. Anonymous says:

    I must concur with Danny, especially considering how dangerous COM is to your tech support resources due to loading broken DLLs into your process (not MS DLLs).

  12. I've been recently working on a project, where we needed to pass/share information between/with application instances running within different terminal sessions. The only way we finally found was to use UDP-sockets: the data amount was small enough not to exceed UDP packet size.

    Is this method OK? Is there anything more suitable?

    The whole task was the following: all application instances share a single list of items; if UserA in terminal session A adds/deletes an item to/from the list, UserB in terminal session B should see the changes as soon as possible.

    Thank you.

  13. Anonymous says:

    @Virtual8086: See global named pipes. But yeah, I've found the UDP datagrams to localhost to be far more reliable to get working. Named pipes seems to have some odd resource allocation glitches that appear to be residue of an older era.

  14. @Joshua: pipes were also not suitable, because we had to send data to multiple application instances (3 and more) while pipes are somewhat like TCP-sockets allowing only 2 sides to be connected.

  15. Anonymous says:

    @Gabe I checked out an old copy of Visual C++ 1.5 and it didn't seem to know about WM_COPYDATA, but it turns out that by comparison I also have a 16-year-old copy of the Platform SDK .HLP (!) file which did know about WM_COPYDATA and claims that Win32s supports it.

  16. OK… now I just learned about some re-entrancy implications that I hadn't really thought about before!  Ouch.

Comments are closed.