PostMessage 的字节数组封送处理

Posted

技术标签:

【中文标题】PostMessage 的字节数组封送处理【英文标题】:Byte array marshaling for PostMessage 【发布时间】:2011-05-31 14:36:28 【问题描述】:

我正在尝试将一些 C++ 代码移植到 C#,我需要做的一件事是使用 PostMessage 将字节数组传递到另一个进程的窗口。我正在尝试将源代码提供给其他程序,以便确切了解它的预期,但与此同时,原始 C++ 代码如下所示:

unsigned long result[5] = 0;
//Put some data in the array
unsigned int res = result[0];
Text winName = "window name";
HWND hWnd = FindWindow(winName.getConstPtr(), NULL);
BOOL result = PostMessage(hWnd, WM_COMMAND, 10, res);

这就是我现在所拥有的:

[DllImport("User32.dll", SetLastError = true, EntryPoint = "FindWindow")]
public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);

[DllImport("User32.dll", SetLastError = true, EntryPoint = "SendMessage")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);

[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT

    public int dwData;
    public int cbData;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]
    public byte[] lpData;


public const int WM_COPYDATA = 0x4A;

public static int sendWindowsByteMessage(IntPtr hWnd, int wParam, byte[] data)

    int result = 0;

    if (hWnd != IntPtr.Zero)
    
        int len = data.Length;
        COPYDATASTRUCT cds;
        cds.dwData = wParam;
        cds.lpData = data;
        cds.cbData = len;
        result = SendMessage(hWnd, WM_COPYDATA, wParam, ref cds);
    

    return result;


//*****//

IntPtr hWnd = MessageHelper.FindWindow(null, windowName);
if (hWnd != IntPtr.Zero)

    int result = MessageHelper.sendWindowsByteMessage(hWnd, wParam, lParam);
    if (result == 0)
    
        int errCode = Marshal.GetLastWin32Error();
    

请注意,我必须从 C++ 中的 PostMessage 切换到 C# 中的 SendMessage

所以现在发生的情况是我得到的结果和 errCode 都为 0,我认为这意味着消息没有被处理 - 实际上查看另一个应用程序,我没有看到预期的响应。我已经验证了hWnd != IntPtr.Zero,所以我认为消息正在发布到正确的窗口,但是消息数据是错误的。任何想法我做错了什么?

更新

在尝试了 cmets 中的建议后,我仍然没有任何运气。这是我目前得到的:

[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT

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


public struct BYTEARRDATA

    public byte[] data;


public static IntPtr IntPtrAlloc<T>(T param)

    IntPtr retval = Marshal.AllocHGlobal(Marshal.SizeOf(param));
    Marshal.StructureToPtr(param, retval, false);
    return (retval);


public static void IntPtrFree(IntPtr preAllocated)

    //Ignores errors if preAllocated is IntPtr.Zero!
    if (IntPtr.Zero != preAllocated)
    
        Marshal.FreeHGlobal(preAllocated); 
        preAllocated = IntPtr.Zero;
    


BYTEARRDATA d;
d.data = data;
IntPtr buffer = IntPtrAlloc(d);

COPYDATASTRUCT cds;
cds.dwData = new IntPtr(wParam);
cds.lpData = buffer;
cds.cbData = Marshal.SizeOf(d);

IntPtr copyDataBuff = IntPtrAlloc(cds);
IntPtr r = SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, copyDataBuff);
if (r != IntPtr.Zero)

    result = r.ToInt32();


IntPtrFree(copyDataBuff);
copyDataBuff = IntPtr.Zero;
IntPtrFree(buffer);
buffer = IntPtr.Zero;

这是一个 64 位进程试图联系一个 32 位进程,所以那里可能有问题,但我不确定是什么。

【问题讨论】:

你为什么要从PostMessage切换到SendMessage 在我的previous question 上查看这个答案 - 长话短说,PostMessage 是异步的,SendMessage 是同步的,并且调用需要同步,以便数据在调用长度内有效。 试图将数据从 64 位进程传递到 32 位进程将是一个问题。您在copyDataBuff 中传递的指针是64 位的,但是您将它传递给一个32 位进程,它不知道如何处理它。如果两个进程都是 32 位,此代码是否有效? 我尝试创建一个包含此代码的 32 位项目,从该项目中,SendMessage 仍然返回 0,因此 32/64 位不是问题,或者不是唯一的问题。 接收程序是否需要 WM_COMMAND?当它期望 WM_COMMAND 时发送 WM_COPYDATA 将不起作用。 【参考方案1】:

问题是COPYDATASTRUCT 应该包含一个指针作为最后一个成员,而您要传递整个数组。

看看 pinvoke.net 上的例子:http://www.pinvoke.net/default.aspx/Structures/COPYDATASTRUCT.html

cmets 后的更多信息:

鉴于这些定义:

const int WM_COPYDATA = 0x004A;

[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT

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

[DllImport("User32.dll", SetLastError = true, EntryPoint = "FindWindow")]
public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);

[DllImport("User32.dll", SetLastError = true, EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);

我可以创建两个 .NET 程序来测试WM_COPYDATA。这是接收器的窗口过程:

protected override void WndProc(ref Message m)

    switch (m.Msg)
    
        case WM_COPYDATA:
            label3.Text = "WM_COPYDATA received!";
            COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT)); 
            byte[] buff = new byte[cds.cbData];
            Marshal.Copy(cds.lpData, buff, 0, cds.cbData);
            string msg = Encoding.ASCII.GetString(buff, 0, cds.cbData);
            label4.Text = msg;
            m.Result = (IntPtr)1234;
            return;
    
    base.WndProc(ref m);

以及使用SendMessage调用它的代码:

Console.WriteLine("0 bit process.", (IntPtr.Size == 4) ? "32" : "64");
Console.Write("Press ENTER to run test.");
Console.ReadLine();
IntPtr hwnd = FindWindow(null, "JimsForm");
Console.WriteLine("hwnd = 0:X", hwnd.ToInt64());
var cds = new COPYDATASTRUCT();
byte[] buff = Encoding.ASCII.GetBytes(TestMessage);
cds.dwData = (IntPtr)42;
cds.lpData = Marshal.AllocHGlobal(buff.Length);
Marshal.Copy(buff, 0, cds.lpData, buff.Length);
cds.cbData = buff.Length;
var ret = SendMessage(hwnd, WM_COPYDATA, 0, ref cds);
Console.WriteLine("Return value is 0", ret);
Marshal.FreeHGlobal(cds.lpData);

当发送方和接收方都是 32 位进程和 64 位进程时,这将按预期工作。 如果两个进程的“位数”不匹配,它将无法工作

这对于 32/64 或 64/32 不起作用的原因有多种。假设您的 64 位程序想要将此消息发送到 32 位程序。 64 位程序传递的lParam 值将是 8 个字节长。但是 32 位程序只能看到它的 4 个字节。所以该程序将不知道从哪里获取数据!

即使这样有效,COPYDATASTRUCT 结构的大小也是不同的。在 32 位程序中,它包含两个指针和一个 DWORD,总大小为 12 个字节。在 64 位程序中,COPYDATASTRUCT 的长度为 20 字节:两个指针,每个 8 字节,以及一个 4 字节长度的值。

你有类似的问题。

我严重怀疑您是否会让WM_COPYDATA 为 32/64 或 64/32 工作。

【讨论】:

这是一个很好的例子 - 我仍然得到 0 作为结果(请参阅我的更新以获取我的最新代码)。我做错了什么明显的错误? @Matt:查看我的附加信息。我认为您无法完成这项工作。 Windows 负责将数据复制到其他进程的地址空间,并存储一个指向本地缓冲区的指针。所以每个进程都会使用一个正确大小的指针供自己使用。目的进程收不到发送方传递的COPYDATASTRUCT! (并且指向发送者地址空间的指针无论如何都是无用的) 我终于能够获得接收程序的源代码,正如@shsmith 和你所想的那样,WM_COPYDATA 是问题所在。由于我无法将接收器更改为接受 WM_COPYDATA,我将不得不尝试寻找另一种解决方案。感谢您的帮助! 我能够使用这种方法使两个 .exe 之间的 32/64 和 64/32 工作:code.msdn.microsoft.com/windowsdesktop/…【参考方案2】:

这将适用于 32 位发送器到 64 位接收器,64 位发送器到 32 位接收器。也可以从 32 到 32 和 64 到 64 工作。您甚至不需要声明 COPYDATASTRUCT。很简单:

    const int WM_COPYDATA = 0x004A;

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(String lpClassName, String lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

    static IntPtr SendMessage(IntPtr hWnd, byte[] array, int startIndex, int length)
    
        IntPtr ptr = Marshal.AllocHGlobal(IntPtr.Size * 3 + length);
        Marshal.WriteIntPtr(ptr, 0, IntPtr.Zero);
        Marshal.WriteIntPtr(ptr, IntPtr.Size, (IntPtr)length);
        IntPtr dataPtr = new IntPtr(ptr.ToInt64() + IntPtr.Size * 3);
        Marshal.WriteIntPtr(ptr, IntPtr.Size * 2, dataPtr);
        Marshal.Copy(array, startIndex, dataPtr, length);
        IntPtr result = SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, ptr);
        Marshal.FreeHGlobal(ptr);
        return result;
    

    private void button1_Click(object sender, EventArgs e)
    
        IntPtr hWnd = FindWindow(null, "Target Window Tittle");
        byte[] data = System.Text.Encoding.ASCII.GetBytes("this is the sample text");
        SendMessage(hWnd, data, 0, data.Length);
    

    protected override void WndProc(ref Message m)
    
        switch (m.Msg)
        
            case WM_COPYDATA:
                byte[] b = new Byte[Marshal.ReadInt32(m.LParam, IntPtr.Size)];
                IntPtr dataPtr = Marshal.ReadIntPtr(m.LParam, IntPtr.Size * 2);
                Marshal.Copy(dataPtr, b, 0, b.Length);
                string str = System.Text.Encoding.ASCII.GetString(b);
                MessageBox.Show(str);
                // m.Result = put result here;
                return;
        
        base.WndProc(ref m);
    

【讨论】:

【参考方案3】:

会不会是 32 位与 64 位的问题?

尝试将 COPYDATASTRUCT 的 dwData 成员设置为 IntPtr 而不是 int。

有关相关问题,请参阅此线程:

http://www.vistax64.com/net-general/156538-apparent-marshalling-related-problem-x64-but-works-x86.html

查看 COPYDATASTRUCT 的原始定义:

http://msdn.microsoft.com/en-us/library/ms649010(VS.85).aspx

下面是 x64 上 ULONG_PTR 的含义:

http://msdn.microsoft.com/en-us/library/aa384255(VS.85).aspx

要存储 64 位指针值,请使用 ULONG_PTR。使用 32 位编译器编译时,ULONG_PTR 值为 32 位,使用 64 位编译器编译时为 64 位。

【讨论】:

这是一个 64 位进程,试图调用 32 位进程,但将 dwData 作为 IntPtr 传递似乎没有帮助(这并不意味着这不是问题)。【参考方案4】:

在你的IntPtrAlloc 函数中,SizeOf(param) 给你什么?我认为这将是对数组的引用的大小,而不是数组内容的大小。所以 Windows 会将一个 .NET 数组引用复制到另一个进程中,这完全没有意义。

固定数组,并使用Marshal.UnsafeAddrOfPinnedArrayElement 得到lpData 的正确值。

【讨论】:

以上是关于PostMessage 的字节数组封送处理的主要内容,如果未能解决你的问题,请参考以下文章

Android 处理蓝牙(例如字节数组)数据的方法,例如十六进制转字节数组

高难度问题,C#结构体的封送 的使用经验总结

将字节数组转换为字符串并返回字节数组的问题

C#字节数组的常用解码处理方法

处理带有小数的字节数组后的 Java Sound API 噪声

处理产生奇怪结果的字节数组的字符串构造函数[重复]