Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marshalling structs from WM_COPYDATA messages

I am trying to get a C# WPF application to communicate with another application written in C using WM_COPYDATA. The C app is trying to send a struct as follows:

typedef struct
{
    int x;
    int y;
    char str[40];
    double d;
    char c;
} DATASTRUCT;

In my C# app I have defined a struct as follows:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct DATASTRUCT
{
    public int x;
    public int y;
    [MarshalAs(UnmanagedType.LPStr, SizeConst=40)]
    public string s;
    public double d;
    public char c;
};

And the code to receive the WM_COPYDATA message is as follows:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
    hwndSource.AddHook(new HwndSourceHook(WndProc));
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    if (msg == 0x4A)
    {
        DATASTRUCT data = (DATASTRUCT)Marshal.PtrToStructure(lParam, typeof(DATASTRUCT));
        this.updateText(data);
        handled = true;
    }

    return (IntPtr)0;
}

I am receiving messages from the C application, but all the data in the struct is gibberish. Previous to this I was able to manually extract an array of bytes from the lParam pointer and then use System.BitConverter and System.Text.Encoding.ACII to interpret the byte array, and that worked pretty well. But now I am trying to do it in a cleaner way and it's just not working.

like image 246
Phil Avatar asked Mar 01 '23 03:03

Phil


2 Answers

After a long time searching for the answer to this question, I realized that I was missing a VERY important step. I feel like an idiot, but here's the answer to my own question.

The C# struct should look like this:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public unsafe struct DataStruct
{
    public int x;
    public int y;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=40)]
    public string s;
    public double d;
    public char c;
};

And another struct must be defined to receive the WM_COPYDATA info. It looks like this:

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

And the WndProc method should be changed to look like this:

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    if (msg == 0x4A)
    {
        CopyDataStruct cps = (CopyDataStruct)Marshal.PtrToStructure(lParam, typeof(CopyDataStruct));
        DataStruct data = (DataStruct)Marshal.PtrToStructure(cps.lpData, typeof(DataStruct));
        updateText(data);
        handled = true;
    }

    return (IntPtr)0;
}

I was using the CopyDataStruct in my previous working solution, and I just forgot to use it in the newer version.

like image 111
Phil Avatar answered Mar 07 '23 22:03

Phil


Part of the problem is that the str member needs to be a ByValTStr not a LPSTR since it's an inlined string array. Try the following definition

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct DATASTRUCT
{
    public int x;
    public int y;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=40)]
    public string s;
    public double d;
    public char c;
};
like image 29
JaredPar Avatar answered Mar 07 '23 22:03

JaredPar