C#和C++之间通过WM_COPYDATA相互传递数据结构
前言:今天真心忍不住要写这篇博客了,原因很简单,前几天在做这方面的通信,调试了好久,各种bug,也是第一次在C#和C++之间通过SendMessage传递数据结构,不知道怎么弄,去度娘了几十篇博客,要么就是文不对题,要么就是残章断句,要么就是互相copy,越看越烦,问题也一时半会儿解决不了,于是冷静下来想想,再好好找找,果然找到了一片我想要的思绪,于是调试调试,终于ok了,今天也是整理下分享出来,希望对你们的疑惑有所帮助……
好了,吐槽完了,言归正传:
介绍: WM_COPYDATA 是Window API发送消息的标志宏,用于本机不同进程之间的通信(当然,本机进程通信有很多种方式,这只是其中之一,至于各自的优缺点这里就不赘述了)
强调一点:发送WM_COPYDATA 消息是进程阻塞的,意思就是调用SendMessage(WM_COPYDATA )时代码是不往下执行的,要等消息发送完毕了,才返回继续执行(具体的解释请参照MSDN官方文档),本人测试了下,无论WM_COPYDATA 是否发送成功都返回0,这尼玛与文档矛盾???所以各位还得亲测一下才行哦!
<一>C++端发送与接收:
1. 发送:(这里无耻的copy下网上通用的代码,难的手动敲了,你们懂得)
- #include <windows.h>
- #include <time.h>
- #include <conio.h>
- #include <stdio.h>
- int main()
- {
- const char szDlgTitle[] = "RecvMessage";
- HWND hSendWindow = GetConsoleWindow ();
- if (hSendWindow == NULL)
- return -1;
- HWND hRecvWindow = FindWindow(NULL, szDlgTitle);
- if (hRecvWindow == NULL)
- return -1;
- char szSendBuf[100];
- time_t timenow;
- COPYDATASTRUCT CopyData;
- for (int i = 0; i < 10; i++)
- {
- time(&timenow);
- sprintf(szSendBuf, "%s", ctime(&timenow));//注意,ctime()返回的字符串后面带了‘\n‘
- CopyData.dwData = i;
- CopyData.cbData = strlen(szSendBuf);
- szSendBuf[CopyData.cbData - 1] = ‘\0‘;
- CopyData.lpData = szSendBuf;
- SendMessage(hRecvWindow, WM_COPYDATA, (WPARAM)hSendWindow, (LPARAM)&CopyData);
- printf("%s\n", szSendBuf);
- Sleep(1000);
- }
- return 0;
- }
2.接收:(这里也是无耻的copy,见谅……)
[cpp] view
plain copy
- #include "stdafx.h"
- #include "resource.h"
- #include <stdio.h>
- BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
- int APIENTRY WinMain(HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPSTR lpCmdLine,
- int nCmdShow)
- {
- // TODO: Place code here.
- DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);
- return 0;
- }
- BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
- {
- const char szDlgTitle[] = "RecvMessage";
- static HWND s_hEditShowRecv;
- switch (message)
- {
- case WM_INITDIALOG:
- SetWindowText(hDlg, szDlgTitle);
- s_hEditShowRecv = GetDlgItem(hDlg, IDC_EDIT_RECVMESSAGE);
- return TRUE;
- case WM_COMMAND:
- switch (LOWORD(wParam))
- {
- case IDOK:
- case IDCANCEL:
- EndDialog(hDlg, LOWORD(wParam));
- return TRUE;
- }
- break;
- case WM_COPYDATA:
- {
- COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;
- char szBuffer[300];
- memset(szBuffer, 0, sizeof(szBuffer));
- sprintf(szBuffer, "dwData:%d cbData:%d\r\nlpData:0x%08x = %s\r\n\r\n",
- pCopyData->dwData, pCopyData->cbData,
- (PVOID)pCopyData->lpData, (char*)pCopyData->lpData);
- //在编辑框中追加数据
- SendMessage(s_hEditShowRecv, EM_SETSEL, (WPARAM)-1, (LPARAM)-1); // (0, -1)表示全选, (-1,任意)表示全不选
- SendMessage(s_hEditShowRecv, EM_REPLACESEL, FALSE, (LPARAM)szBuffer);
- SendMessage(s_hEditShowRecv, EM_SCROLLCARET, 0, 0);
- }
- return TRUE;
- }
- return FALSE;
- }
- 以上为C++端发送与接收,如果C++接收C#发过来的数据,也差不多是这样,C#什么结构体,就对应C++的结构体,值得注意的是C#中的String要对应C++的byte[]数组,char[]也可以,就是字节数组,一个元素占一个字节
可以参照这篇博客:http://codego.net/511178/
也就是它给了我灵感,再次谢谢翻译这篇博客的兄弟!
<二>C#端发送与接收:(如果在与C++通信时,请格外注意)
以下我只贴关键实例代码,完整的请参照上面那篇博客和这篇http://www.cnblogs.com/sbCat/p/5257521.html
1. 发送:
//这里COPYDATASTRUCT对应C++的COPYDATASTRUCT,只不过是把它转为C#结构体 //注意结构体上面要加上[StructLayout(LayoutKind.Sequential)],表示结构体为顺序布局 [StructLayout(LayoutKind.Sequential)] public struct COPYDATASTRUCT { public IntPtr dwData;//用户定义数据 public int cbData;//用户定义数据的长度 public IntPtr lpData; } //测试要发送的结构体(如果要发送到C++,那么C++端也要定义对应的结构体) [StructLayout(LayoutKind.Sequential)] public unsafe struct IPC_Header { public int wVersion; public int wPacketSize; public int wMainCmdID; public int wSubCmdID; } //注意下面的name是string类型,在C#中string是引用类型 //我理解为C++的引用类型吧,对其sizeof大小为4,差不多是指针的意思吧,个人鄙见,方便理解,别喷我…… //如果传递到C++,就有问题了,若该string很大,比如“123456789”这sizeof字节数显然不止是4,所以限制大小,这里测试设置为32 [StructLayout(LayoutKind.Sequential)] public unsafe struct IPC_Package { public IPC_Header Head; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string name; } //给IPCBuffer结构赋值 IPC_Package IPCBuffer = new IPC_Package(); IPCBuffer.Head.wVersion = 12345; IPCBuffer.Head.wSubCmdID = 54321; IPCBuffer.Head.wMainCmdID = 666; IPCBuffer.Head.wPacketSize = Marshal.SizeOf(IPCBuffer); IPCBuffer.name = "wocaowocaowocaocaocao"; //IPCBuffer结构体转换IntPtr 类型的指针 //作为CopyDataStruct.lpData的值 int cbSize = IPCBuffer.Head.wPacketSize; IntPtr structPtr = Marshal.AllocHGlobal(cbSize); Marshal.StructureToPtr(IPCBuffer, structPtr, true); //给COPYDATASTRUCT 结构赋值 //注意CopyDataStruct.cbData这个字段要注意,是你要发送的结构体的大小,别算错了,否则部分会乱码 //C++与C#之间直接传递数据貌似很严格,一个字节都不能错 //这里顺便回顾下上面那个IPC_Package结构的name字符串,是不是觉得限制了大小很明智?直接Marshal.SizeOf()就能准确求出大小? COPYDATASTRUCT CopyDataStruct; CopyDataStruct.lpData = (IntPtr)structPtr; CopyDataStruct.dwData = (IntPtr)9998877; CopyDataStruct.cbData = cbSize; //赋值完了,把要发送的COPYDATASTRUCT 创建一份“非托管内存”,然后赋值发送出去 //因为C#的是托管内存,有自己的内存回收机制,脚本啥之类的都差不多有“自动回收机制”吧 //而C++ new出来的都是“非托管内存”,因为要自己手动delete掉,说白点就是不让系统托管我new的内存,我想干啥就干啥 //以上为个人理解,非专业,别喷我…… IntPtr iPtr = Marshal.AllocHGlobal(Marshal.SizeOf(CopyDataStruct)); Marshal.StructureToPtr(CopyDataStruct, iPtr, true); //最终的发送 SendMessage(m_hWnd, WM_COPYDATA, IntPtr.Zero, iPtr); //因为SendMessage是阻塞的,所以执行到这儿表示发送完毕 //删除创建的“非托管内存”(因为你new了,所以要delete,这点就类似C++的风格了,注意这里是“非托管内存”哦,用完要释放哦) System.Runtime.InteropServices.Marshal.FreeCoTaskMem(iPtr); System.Runtime.InteropServices.Marshal.FreeCoTaskMem(structPtr); 以上为C#端发送,值得注意的就是: 1>C#的结构体定义时要设置内存布局为顺序布局(即[StructLayout(LayoutKind.Sequential)])。 2>如果结构体有字符串,记得要设置其大小(即[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)])。 3>如果是C#端发送到C++端,记得把要发送的COPYDATASTRUCT 对象开辟一段“非托管内存”,然后赋值发送,因为C#内存机制为自动回收(就是系统帮你托 管了,你不必担心内存泄漏问题),这样若果你不开辟直接发送的话,出了这个函数作用域,局部内存就会被回收,也就发送不到C++端了(你可以理解 为C++的局部变量的意思),因此要用Marshal.AllocHGlobal一份,赋值在发送,发送完记得释放掉
2. 接收:
//钩子类型(监视SendMessage消息的传递)
private const int WH_CALLWNDPROC = 4; //钩子类型(监视SendMessage消息的传递) private const int WM_COPYDATA = 0x004A; //消息类型 //C#端钩子截获的消息的结构(对应WH_CALLWNDPROC) //mbd 这个结构我找了好久,什么钩子对应什么结构 //网上只有监听鼠标啊,键盘啥的钩子结构,很少有监听SendMessage消息的钩子结构,为此度娘了一番,msdn了一番, //找到钩子回调的原型函数ShellPro,然后几经周折发现CWPSTRUCT这个结构,看着有点儿眼熟,发现是上面那篇博客有提到过, //于是再看了看,尼玛有点怪,于是在msdn该结构类型,加上[StructLayout(LayoutKind.Sequential)], //转换C#类型,调试,然后终于是ok了 [StructLayout(LayoutKind.Sequential)] public struct CWPSTRUCT { public IntPtr lParam; public IntPtr wParam; public uint message; public IntPtr hwnd; } private unsafe int Hook(int nCode, int wParam, int lParam) { try { IntPtr param = new IntPtr(lParam); CWPSTRUCT cwStruct = (CWPSTRUCT)Marshal.PtrToStructure(param, typeof(CWPSTRUCT)); if (cwStruct.message == WM_COPYDATA) { Delog.text = "发送消息成功!"; COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure((IntPtr)cwStruct.lParam, typeof(COPYDATASTRUCT)); byte[] bt = new byte[cds.cbData]; Marshal.Copy(cds.lpData, bt, 0, bt.Length); string str = System.Text.Encoding.Default.GetString (bt); Debug.Log("字符串为:" + str); } if (CallNextProc) { return CallNextHookEx(idHook, nCode, wParam, lParam); } else { //return 1; return CallNextHookEx(idHook, nCode, wParam, lParam); } } catch (Exception ex) { Debug.Log(ex.Message); Delog.text = ex.Message; return 0; } }
好了,o了,以上为个人测试的结果,只取了部分测试代码,相信聪明的你只需要相应的伪代码,看看流程啥的,你就懂了,具体的多调试调试就好了,还有顺便去看看我之前参考的两篇博客,虽有瑕疵,但很不错,给了我很多灵感,在此谢谢两位了!
小弟我也是第一次接触C#和Unity3D,没办法,项目需求没人搞,期间遇到各种困难,哎,调试查资料搞了2天,总算是通了,爽,因为之前在网上查的我TMD蛋都碎了,各种千篇一律,文不对题,模棱两可,错的也乱帖,越看越傻逼!哎,苦逼了我们这些新手,所以才下决心把我整个流程的思绪整理下,分享给大家看看,水平有限,见笑了。
写这篇博客吐槽了很多,总之在寻求真理的路上也收获颇多,希望与大家一同进步!