Search code examples
c#winformspinvokewindows-messageswm-copydata

Why the WM_COPYDATA message is not being received when a data payload is provided?


I have a pretty well-known setup for the inter-process data exchange using the (ugly) WM_COPYDATA message. It's not my decision, I have to support it in a legacy app.

const uint WM_COPYDATA = 0x004A;

[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
  public uint dwData;
  public int cbData;
  public IntPtr lpData;
}

[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hwnd, uint msg, IntPtr wparam, ref COPYDATASTRUCT lparam);

If I'm sending just a plain struct, without attaching the additional data, it works just fine:

COPYDATASTRUCT container;
container.dwData = 42;
container.cbData = 0;
container.lpData = IntPtr.Zero;
SendMessage(myHwnd, WM_COPYDATA, IntPtr.Zero, ref container);

On the receiver side (an external WinForms app), I get this message and can correctly read the dwData field:

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_COPYDATA)
    {
        var container = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
        MessageBox.Show(container.dwData.ToString()); // 42
    }
    base.WndProc(ref m);
}

But as soon as I attach some additional payload, the external application stops receiving this message. The m.Msg == WM_COPYDATA condition is always false in the receiver window proc, and the sender gets a 0 result from the SendMessage call.

COPYDATASTRUCT container;
container.dwData = 42;
container.cbData = 4;
container.lpData = Marshal.AllocHGlobal(4);
int result = SendMessage(hwnd, WM_COPYDATA, IntPtr.Zero, ref container); // 0

(Actually, this is a dummy payload, but with the real one, it's the same.)

string payload = "test";
container.cbData = (payload.Length + 1) * 2;
container.lpData = Marshal.StringToHGlobalUni(payload);

I have also tried to marshal the COPYDATASTRUCT manually (using Marshal.StructToPtr) by changing the last parameter type of the SendMessage to IntPtr, unfortunately with no success (the same behavior).

I have tried to rely on the CLR marshalling by changing the COPYDATASTRUCT definition:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct COPYDATASTRUCT
{
  public uint dwData;
  public int cbData;

  [MarshalAs(UnmanagedType.LPWStr)]
  public string lpData;
}

Guess what? No effect.

What's wrong with my setup? Why the message cannot be received when a payload is attached to the data struct?


Solution

  • The structure definition is incorrect, it should be

    [StructLayout(LayoutKind.Sequential)]
    struct COPYDATASTRUCT
    {
      public IntPtr dwData; // in C/C++ this is an UINT_PTR, not an UINT
      public int cbData;
      public IntPtr lpData;
    }
    

    Your definition of dwData was matching ok in a 32-bit process (4-bytes), but not in a 64-bit process (8 bytes). Since this is the first field in the structure, all bets are off when its definition is incorrect.