Sending a struct from C++ to WPF using WM_COPYDATA

Matthew Olenik picture Matthew Olenik · Dec 11, 2009 · Viewed 7.3k times · Source

I have a native C++ application that, for the time being simply needs to send its command line string and current mouse cursor coordinates to a WPF application. The message is sent and received just fine, but I cannot convert the IntPtr instance in C# to a struct.

When I try to do so, the application will either crash without exception or the line of code that converts it is skipped and the next message in the loop is received. This probably means there's a native exception occurring, but I don't know why.

Here's the C++ program. For the time being I'm ignoring the command line string and using fake cursor coordinates just to make sure things work.

#include "stdafx.h"
#include "StackProxy.h"
#include "string"

typedef std::basic_string<WCHAR, std::char_traits<WCHAR>> wstring;

struct StackRecord
{
    //wchar_t CommandLine[128];
    //LPTSTR CommandLine;
    //wstring CommandLine;
    __int32 CursorX;
    __int32 CursorY;
};

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    COPYDATASTRUCT data;
    ZeroMemory(&data, sizeof(COPYDATASTRUCT));

    StackRecord* record = new StackRecord();

    wstring cmdLine(lpCmdLine);
    //record.CommandLine = cmdLine;
    record->CursorX = 5;
    record->CursorY = 16;
    data.dwData = 12;
    data.cbData = sizeof(StackRecord);
    data.lpData = record;

    HWND target = FindWindow(NULL, _T("Window1"));

    if(target != NULL)
    {
        SendMessage(target, WM_COPYDATA, (WPARAM)(HWND) target, (LPARAM)(LPVOID) &data);
    }
    return 0;
}

And here is the part of the WPF application that receives the message. The second line inside the IF statement is skipped over, if the whole thing doesn't just crash.

    public IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == Interop.WM_COPYDATA)
        {
            var data = (Interop.CopyDataStruct)Marshal.PtrToStructure(lParam, typeof(Interop.CopyDataStruct));
            var record = (Interop.StackRecord)Marshal.PtrToStructure(data.lpData, typeof(Interop.StackRecord));
            MessageBox.Show(String.Format("X: {0}, Y: {1}", record.CursorX, record.CursorY));
        }
        return IntPtr.Zero;
    }

And here are the C# definitions for the structs. I have toyed endlessly with marshalling attributes and gotten nowhere.

internal static class Interop
{
    public static readonly int WM_COPYDATA = 0x4A;

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct CopyDataStruct
    {
        public IntPtr dwData;
        public int cbData;
        public IntPtr lpData;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
    public struct StackRecord
    {
        //[MarshalAs(UnmanagedType.ByValTStr)]
        //public String CommandLine;
        public Int32 CursorX;
        public Int32 CursorY;
    }
}

Any ideas?

Answer

tyranid picture tyranid · Dec 14, 2009

I am not sure what you are getting wrong necessarily without more info about your setup. I replicated the code as best I could (using WndProc in a WPF app, sending from my own win32 app) and it works fine for me. There are a few errors which will definetly crop up if you are running 64 bit applications, namely the Pack = 1 will cause the COPYDATASTRUCT to become misaligned and reading from the pointer is likely to end in pain.

It is crashing passing just the ints? Looking at your commented code passing a LPWSTR or wstring is going to cause serious issues, although that shouldn't become apparent until you unmarshal the sent data.

For what it is worth, this is snippets of my code which seem to work for me including getting the command line across.

/* C++ code */
struct StackRecord
{
    wchar_t cmdline[128];
    int CursorX;
    int CursorY;
};

void SendCopyData(HWND hFind)
{
    COPYDATASTRUCT cp;
    StackRecord record;

    record.CursorX = 1;
    record.CursorY = -1;

    _tcscpy(record.cmdline, L"Hello World!");
    cp.cbData = sizeof(record);
    cp.lpData = &record
    cp.dwData = 12;
    SendMessage(hFind, WM_COPYDATA, NULL, (LPARAM)&cp);
}

/* C# code */
public static readonly int WM_COPYDATA = 0x4A;

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

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct StackRecord
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
    public string CommandLine;
    public Int32 CursorX;
    public Int32 CursorY;
}

protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    if (msg == WM_COPYDATA)
    {
        StackRecord record = new StackRecord();
        try
        {
            CopyDataStruct cp = (CopyDataStruct)Marshal.PtrToStructure(lParam, typeof(CopyDataStruct));
            if (cp.cbData == Marshal.SizeOf(record))
            {
                record = (StackRecord)Marshal.PtrToStructure(cp.lpData, typeof(StackRecord));
            }
        }
        catch (Exception e)
        {
            System.Diagnostics.Debug.WriteLine(e.ToString());
        }
        handled = true;
    }
    else
    {
        handled = false;
    }
    return IntPtr.Zero;
}