C#和C++之间通过WM_COPYDATA相互传递数据结构


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下网上通用的代码,难的手动敲了,你们懂得大笑

  1. #include <windows.h>  
  2. #include <time.h>  
  3. #include <conio.h>  
  4. #include <stdio.h>  
  5. int main()  
  6. {  
  7.     const char szDlgTitle[] = "RecvMessage";  
  8.   
  9.     HWND hSendWindow = GetConsoleWindow ();  
  10.     if (hSendWindow == NULL)  
  11.         return -1;  
  12.     HWND hRecvWindow = FindWindow(NULL, szDlgTitle);  
  13.     if (hRecvWindow == NULL)  
  14.         return -1;  
  15.   
  16.     char szSendBuf[100];  
  17.     time_t  timenow;  
  18.     COPYDATASTRUCT CopyData;  
  19.   
  20.     for (int i = 0; i < 10; i++)  
  21.     {  
  22.         time(&timenow);  
  23.         sprintf(szSendBuf, "%s", ctime(&timenow));//注意,ctime()返回的字符串后面带了'\n'  
  24.         CopyData.dwData = i;  
  25.         CopyData.cbData = strlen(szSendBuf);  
  26.         szSendBuf[CopyData.cbData - 1] = '\0';  
  27.         CopyData.lpData = szSendBuf;  
  28.   
  29.         SendMessage(hRecvWindow, WM_COPYDATA, (WPARAM)hSendWindow, (LPARAM)&CopyData);  
  30.         printf("%s\n", szSendBuf);  
  31.         Sleep(1000);  
  32.     }  
  33.     return 0;  
  34. }  


          2.接收:(这里也是无耻的copy,见谅……)

[cpp]  view plain  copy
  1. #include "stdafx.h"  
  2. #include "resource.h"  
  3. #include <stdio.h>  
  4. BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);  
  5. int APIENTRY WinMain(HINSTANCE hInstance,  
  6.                      HINSTANCE hPrevInstance,  
  7.                      LPSTR     lpCmdLine,  
  8.                      int       nCmdShow)  
  9. {  
  10.     // TODO: Place code here.  
  11.     DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);  
  12.     return 0;  
  13. }  
  14. BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)  
  15. {  
  16.     const char szDlgTitle[] = "RecvMessage";  
  17.     static HWND s_hEditShowRecv;  
  18.   
  19.     switch (message)  
  20.     {  
  21.     case WM_INITDIALOG:  
  22.         SetWindowText(hDlg, szDlgTitle);  
  23.         s_hEditShowRecv = GetDlgItem(hDlg, IDC_EDIT_RECVMESSAGE);  
  24.         return TRUE;  
  25.   
  26.     case WM_COMMAND:  
  27.         switch (LOWORD(wParam))  
  28.         {  
  29.         case IDOK:  
  30.         case IDCANCEL:  
  31.             EndDialog(hDlg, LOWORD(wParam));  
  32.             return TRUE;  
  33.         }  
  34.         break;  
  35.   
  36.     case WM_COPYDATA:  
  37.         {  
  38.             COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;  
  39.             char szBuffer[300];  
  40.   
  41.             memset(szBuffer, 0, sizeof(szBuffer));  
  42.             sprintf(szBuffer, "dwData:%d cbData:%d\r\nlpData:0x%08x = %s\r\n\r\n",   
  43.                 pCopyData->dwData, pCopyData->cbData,   
  44.                 (PVOID)pCopyData->lpData, (char*)pCopyData->lpData);  
  45.             //在编辑框中追加数据  
  46.             SendMessage(s_hEditShowRecv, EM_SETSEL, (WPARAM)-1, (LPARAM)-1); // (0, -1)表示全选, (-1,任意)表示全不选  
  47.             SendMessage(s_hEditShowRecv, EM_REPLACESEL, FALSE, (LPARAM)szBuffer);  
  48.             SendMessage(s_hEditShowRecv, EM_SCROLLCARET, 0, 0);  
  49.         }  
  50.         return TRUE;  
  51.     }  
  52.     return FALSE;  
  53. }  
  54. 以上为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
<pre name="code" class="csharp">[StructLayout(LayoutKind.Sequential)]
public unsafe struct IPC_Package
{
	public IPC_Header Head;
	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
	public string name;
   //若果要传byte数组,就这样定义
   //[MarshalAs(UnmanagedType.ByValArray, SizeConst = IPC_BUF_SIZE)]
   //public byte[] bData;
}
 
 
//给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蛋都碎了,各种千篇一律,文不对题,模棱两可,错的也乱帖,越看越傻逼!哎,苦逼了我们这些新手,所以才下决心把我整个流程的思绪整理下,分享给大家看看,水平有限,见笑了。
        写这篇博客吐槽了很多,总之在寻求真理的路上也收获颇多,希望与大家一同进步!

猜你喜欢

转载自blog.csdn.net/dominating_/article/details/51941515