Search code examples
c#commarshalling

COPYDATASTRUCT and Marshal.PtrToStructure throws AccessViolationException


My 64-bit C# 4.5.1 WinForms application, using the following code, correctly gets a COPYDATASTRUCT message from an external 32-bit C++ DLL (over which I have no control), as shown in a Memory Window using the address stored in lpData:

protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case (int)WM.COPYDATA:
            var msg = Marshal.PtrToStructure<COPYDATASTRUCT>(m.LParam);
            Console.WriteLine(string.Format(
                "msg = [" + Environment.NewLine +
                "           dwData = {0}" + Environment.NewLine +
                "           cbData = {1}" + Environment.NewLine +
                "           lpData = {2}" + Environment.NewLine +
                "      ]", msg.dwData, msg.cbData, msg.lpData.ToString("x16")));
        var pData = Marshal.PtrToStructure<GEORECT>(msg.lpData);
        // Code to fetch data
        break;
    }
}

The output window shows that the memory address passed in contains the correct data, as described by the following structure:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct COPYDATASTRUCT
{
    public IntPtr dwData { get; private set; }
    public int cbData { get; private set; }
    public IntPtr lpData { get; private set; }
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct GEORECT
{
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 11)]
    public string Falconview;               // "FALCONVIEW\0"

    public MessageType Type { get; set; }   // MessageType.FV_RUBBERBAND_GEORECT_MSG (3)
    public int MessageId { get; set; }
    public double NW_Latitude { get; set; }
    public double NW_Longitude { get; set; }
    public double SE_Latitude { get; set; }
    public double SE_Longitude { get; set; }
}

Yet the Marshal.PtrToStructure always throws the AccessViolationException though I can see the data is correct: the byte count for the structure size returns 49, and there are 49 bytes in correct format.

I have checked that the native and managed structs are the same size and have appropriate attributes for the included C string, and have looked for examples such as Marshal.PtrToStructure throws exception but they do not shed light on my problem.

I surmise that perhaps the problem is that the 32-bit address is not being correctly cast to an IntPtr and used in 64-bit address space, but I cannot verify this, because the 64-bit address (with high zero bits) works in the VS debugger window.

Can anyone help?


Solution

  • According to the documentation for the UnmanagedType Enumeration, if the string is a fixed-length character array that is stored completely inside the struct (which I'm guessing it is) instead of a pointer to a string stored elsewhere, you should be using UnmanagedType.ByValTStr instead of UnmanagedType.LPStr in the MarshalAs attribute for the Falconview property. While I was worried for a second that the marshaller might marshal the string as Unicode instead of ANSI because of the TStr bit, the documentation says that the marshaller will refer to the CharSet you already include in your StructLayout attribute for that determination, so that shouldn't be an issue. Unfortunately, I'm not sure how to go about testing this solution out since I'm not sure what library you're using, so let me know whether or not that works for you.