使用 user32 SendMessage 调用 Marshal.Copy 时的 AccessViolation

Posted

技术标签:

【中文标题】使用 user32 SendMessage 调用 Marshal.Copy 时的 AccessViolation【英文标题】:AccessViolation when calling Marshal.Copy using user32 SendMessage 【发布时间】:2014-10-15 05:02:58 【问题描述】:

我正在为我的组织编写一个新工具,它必须通过 SendMessage 与旧工具通信。

我刚刚使用此处的代码制作了一个测试应用程序: http://www.c-sharpcorner.com/Blogs/6444/

我已编辑发送代码以符合我的目的。但是我在接收“GetMessage”表单中的消息时遇到了一些问题。消息确实通过了,但程序在尝试将数据转换为字符串时中断。

这是我的代码:

发送:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, ref COPYDATASTRUCT lParam);

[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

private void button1_Click(object sender, EventArgs e)

    IntPtr hwnd = FindWindow(null, "GetMessage");

    if (hwnd != null)
                
        string message = textBox1.Text + "-" + System.DateTime.Now.ToString();
        COPYDATASTRUCT cds;
        cds.dwData = 0;
        cds.lpData = (int)Marshal.StringToHGlobalAnsi(message);
        cds.cbData = message.Length;
        SendMessage(hwnd, (int)WM_COPYDATA, 0, ref cds);
    

收到:

private const int WM_COPYDATA = 0x4A;

[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT

    public int dwData;
    public int cbData;
    public int lpData;


protected override void WndProc(ref Message m)

    switch (m.Msg)
    
        case WM_COPYDATA:

            COPYDATASTRUCT CD = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
            byte[] B = new byte[CD.cbData];
            IntPtr lpData = new IntPtr(CD.lpData);

            //string test = Marshal.PtrToStringAuto(lpData, CD.lpData); // this doesn't work either
            Marshal.Copy(lpData, B, 0, CD.cbData); // access violation here
            string strData = Encoding.Default.GetString(B);
            listBox1.Items.Add(strData);

            break;

        default:
            base.WndProc(ref m);
            break;
    

我得到的错误:

试图读取或写入受保护的内存。这通常是一个 指示其他内存已损坏。

我尝试将 send 中的消息字符串设置为字段(因此它不会超出范围),将 Send Message 的“int wParam”参数设置为“IntPtr”并使用 IntPtr.Zero 而不是0 在我的 SendMessage 调用中,并以 null 终止字符串,如下所示:

cds.lpData = (int)Marshal.StringToHGlobalAnsi(message + '\0');
cds.cbData = (message+'\0').Length;

仍然遇到同样的问题。

【问题讨论】:

你试过Marshal.PtrToStringAnsi吗? 您的声明是错误的,此代码无法在 64 位模式下运行,并且无论如何尝试都会崩溃。从pinvoke.net 网站获得更好的。 【参考方案1】:

我解决了这个问题,我认为问题在于我在 SendMessage 和 COPYDATASTRUCT 中使用了 int 而不是 IntPtr。

我从http://pinvoke.net/default.aspx/user32.SendMessage 和http://pinvoke.net/default.aspx/Structures/COPYDATASTRUCT.html 获得此信息

这是工作代码:

private const int WM_COPYDATA = 0x004A;

[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT

    public IntPtr dwData;
    public int cbData;
    public IntPtr lpData;

发送:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
//static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); // original
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, ref COPYDATASTRUCT cds); // override

private void SendMsg()

    if (_hwnd != null)
    
        COPYDATASTRUCT cds;
        cds.dwData = IntPtr.Zero;
        cds.lpData = Marshal.StringToHGlobalAnsi(stockCode);
        cds.cbData = stockCode.Length;
        SendMessage(_hwnd, (int) WM_COPYDATA, IntPtr.Zero, ref cds);
    

收到:

protected override void WndProc(ref Message m)

    switch (m.Msg)
    
        case WM_COPYDATA:

            COPYDATASTRUCT CD = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
            byte[] B = new byte[CD.cbData];
            IntPtr lpData = CD.lpData;

            Marshal.Copy(lpData, B, 0, CD.cbData);
            string strData = Encoding.Default.GetString(B);
            listBox1.Items.Add(strData);

            break;

        default:
            base.WndProc(ref m);
            break;
    

我相信这适用于 64 位 Windows。

【讨论】:

两个潜在的陷阱。 (a) 内存泄漏,因为您的代码没有显式释放 StringToHGlobalAnsi 分配的内存;有关示例,请参见在线参考(但在接收代码中发布!)。 (b) 在代码页是多字节的机器上,例如中文,复制时字符串将被截断,因为您计算的是 .NET 字符而不是编码字节。使用 PtrToHGlobalAnsi 并忽略 cbData 可能更好(如果您关心这种情况)。 PtrToStringAnsi,而不是 PtrToHGlobalAnsi。 谢谢@groverboy。我只写发送代码,“接收”部分实际上是遗留代码(我写了上面的接收来测试我的代码)。我是否需要担心上面发送代码中的任何内存泄漏?我将与他们再次确认现有的旧接收代码中没有内存泄漏。 更正:我忘了SendMessage是同步的,这意味着发送代码可以释放分配的内存。事实上,它应该,正如WM_COPYDATA message 中所述。这是另一个示例,它也正确声明了 COPYDATASTRUCT:Interprocess Communication Between .NET and MFC Using WM_COPYDATA。

以上是关于使用 user32 SendMessage 调用 Marshal.Copy 时的 AccessViolation的主要内容,如果未能解决你的问题,请参考以下文章

User32.dll 的 SendMessage 方法返回错误的 ListViewGroup id

如何使用 PostMessage/SendMessage User32 单击菜单项

user32.dll SendMessage:以大写字符发送的字符串...但区分大小写的字符串

c# 调用 win32 API的 SendMessage 函数 ,里面的属性用法?

未找到 Java JNA sendMessage()

使用 PostMessage() 或 SendMessage() 发送大写字母