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 处理蓝牙(例如字节数组)数据的方法,例如十六进制转字节数组