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下网上通用的代码,难的手动敲了,你们懂得)
2.接收:(这里也是无耻的copy,见谅……)
<二>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;
//若果要传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蛋都碎了,各种千篇一律,文不对题,模棱两可,错的也乱帖,越看越傻逼!哎,苦逼了我们这些新手,所以才下决心把我整个流程的思绪整理下,分享给大家看看,水平有限,见笑了。
写这篇博客吐槽了很多,总之在寻求真理的路上也收获颇多,希望与大家一同进步!