COPYDATASTRUCT 和 Marshal.PtrToStructure 抛出 AccessViolationException
COPYDATASTRUCT and Marshal.PtrToStructure throws AccessViolationException
我的 64 位 C# 4.5.1 WinForms 应用程序使用以下代码从外部 32 位 C++ DLL(我无法控制)正确获取 COPYDATASTRUCT 消息,如内存 Window 使用存储在 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;
}
}
输出window表明传入的内存地址包含正确的数据,如下结构所述:
[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[=13=]"
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; }
}
然而 Marshal.PtrToStructure 总是抛出 AccessViolationException,尽管我可以看到数据是正确的:结构大小的字节数 returns 49,并且有 49 个字节的格式正确。
我已检查本机结构和托管结构是否具有相同的大小,并且对所包含的 C 字符串具有适当的属性,并查找了 Marshal.PtrToStructure throws exception 等示例,但它们没有阐明我的问题。
我推测问题可能是 32 位地址没有被正确地转换为 IntPtr 并在 64 位地址空间中使用,但我无法验证这一点,因为 64 位地址(具有高零位)在 VS 调试器中工作 window.
有人能帮忙吗?
根据 UnmanagedType Enumeration 的文档,如果字符串是一个固定长度的字符数组,它完全存储在结构内部(我猜它是)而不是指向字符串的指针存储在别处,您应该在 Falconview 属性 的 MarshalAs 属性中使用 UnmanagedType.ByValTStr 而不是 UnmanagedType.LPStr。虽然我担心编组器可能会因为 TStr 位而将字符串编组为 Unicode 而不是 ANSI,但文档说编组器将参考您已经包含在 StructLayout 属性中的 CharSet 来确定该决定,因此应该成为问题。不幸的是,我不确定如何测试这个解决方案,因为我不确定你使用的是什么库,所以让我知道它是否适合你。
我的 64 位 C# 4.5.1 WinForms 应用程序使用以下代码从外部 32 位 C++ DLL(我无法控制)正确获取 COPYDATASTRUCT 消息,如内存 Window 使用存储在 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;
}
}
输出window表明传入的内存地址包含正确的数据,如下结构所述:
[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[=13=]"
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; }
}
然而 Marshal.PtrToStructure 总是抛出 AccessViolationException,尽管我可以看到数据是正确的:结构大小的字节数 returns 49,并且有 49 个字节的格式正确。
我已检查本机结构和托管结构是否具有相同的大小,并且对所包含的 C 字符串具有适当的属性,并查找了 Marshal.PtrToStructure throws exception 等示例,但它们没有阐明我的问题。
我推测问题可能是 32 位地址没有被正确地转换为 IntPtr 并在 64 位地址空间中使用,但我无法验证这一点,因为 64 位地址(具有高零位)在 VS 调试器中工作 window.
有人能帮忙吗?
根据 UnmanagedType Enumeration 的文档,如果字符串是一个固定长度的字符数组,它完全存储在结构内部(我猜它是)而不是指向字符串的指针存储在别处,您应该在 Falconview 属性 的 MarshalAs 属性中使用 UnmanagedType.ByValTStr 而不是 UnmanagedType.LPStr。虽然我担心编组器可能会因为 TStr 位而将字符串编组为 Unicode 而不是 ANSI,但文档说编组器将参考您已经包含在 StructLayout 属性中的 CharSet 来确定该决定,因此应该成为问题。不幸的是,我不确定如何测试这个解决方案,因为我不确定你使用的是什么库,所以让我知道它是否适合你。