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. HWND hSendWindow = GetConsoleWindow ();
  9. if (hSendWindow == NULL)
  10. return -1;
  11. HWND hRecvWindow = FindWindow(NULL, szDlgTitle);
  12. if (hRecvWindow == NULL)
  13. return -1;
  14. char szSendBuf[100];
  15. time_t  timenow;
  16. COPYDATASTRUCT CopyData;
  17. for (int i = 0; i < 10; i++)
  18. {
  19. time(&timenow);
  20. sprintf(szSendBuf, "%s", ctime(&timenow));//注意,ctime()返回的字符串后面带了‘\n‘
  21. CopyData.dwData = i;
  22. CopyData.cbData = strlen(szSendBuf);
  23. szSendBuf[CopyData.cbData - 1] = ‘\0‘;
  24. CopyData.lpData = szSendBuf;
  25. SendMessage(hRecvWindow, WM_COPYDATA, (WPARAM)hSendWindow, (LPARAM)&CopyData);
  26. printf("%s\n", szSendBuf);
  27. Sleep(1000);
  28. }
  29. return 0;
  30. }

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. switch (message)
  19. {
  20. case WM_INITDIALOG:
  21. SetWindowText(hDlg, szDlgTitle);
  22. s_hEditShowRecv = GetDlgItem(hDlg, IDC_EDIT_RECVMESSAGE);
  23. return TRUE;
  24. case WM_COMMAND:
  25. switch (LOWORD(wParam))
  26. {
  27. case IDOK:
  28. case IDCANCEL:
  29. EndDialog(hDlg, LOWORD(wParam));
  30. return TRUE;
  31. }
  32. break;
  33. case WM_COPYDATA:
  34. {
  35. COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;
  36. char szBuffer[300];
  37. memset(szBuffer, 0, sizeof(szBuffer));
  38. sprintf(szBuffer, "dwData:%d cbData:%d\r\nlpData:0x%08x = %s\r\n\r\n",
  39. pCopyData->dwData, pCopyData->cbData,
  40. (PVOID)pCopyData->lpData, (char*)pCopyData->lpData);
  41. //在编辑框中追加数据
  42. SendMessage(s_hEditShowRecv, EM_SETSEL, (WPARAM)-1, (LPARAM)-1); // (0, -1)表示全选, (-1,任意)表示全不选
  43. SendMessage(s_hEditShowRecv, EM_REPLACESEL, FALSE, (LPARAM)szBuffer);
  44. SendMessage(s_hEditShowRecv, EM_SCROLLCARET, 0, 0);
  45. }
  46. return TRUE;
  47. }
  48. return FALSE;
  49. }
  50. 以上为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蛋都碎了,各种千篇一律,文不对题,模棱两可,错的也乱帖,越看越傻逼!哎,苦逼了我们这些新手,所以才下决心把我整个流程的思绪整理下,分享给大家看看,水平有限,见笑了。

写这篇博客吐槽了很多,总之在寻求真理的路上也收获颇多,希望与大家一同进步!

时间: 2024-11-05 23:36:35

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

Express框架与html之间如何进行数据传递

关于Node.js 的Express框架介绍,推荐看菜鸟教程的Express框架,很适合入门,这里不再赘述,这里主要讲一下Express框架与html之间如何进行数据传递 我采用的是JQuery的Ajax()向后台传参方式 (url传参) 一.首先先讲一下jQuery的Ajax()向后台传参(参考http://blog.csdn.net/ailo555/article/details/48859425) 1.Type属性为Get时: (1)第一种方法:(通过url传参) function Get

在activity之间通过静态变量传递数据

在activity之间通过静态变量传递数据 一.简介 主要作用:解决intent不能传递非序列化的对象 评价:简单方便,不过intent方式更加简单和方便 二.具体操作 1.在传输数据的页面弄好数据,传递给接收数据的页面 Obj1 obj=new Obj1("fry",22); Activity01.obj=obj; 2.在接收数据的页面显示数据 输出obj即可 3.具体代码 传输数据的页面 Intent intent=new Intent();//初始化intent intent.s

ionic新手教程第七课-简要说明几种界面之间的參数传递及优缺点

截至2016年4月13日19点32分,我公布的ionic新手教程,已经公布6课了, 总訪问量将近6000,平均每节课能有1000的訪问量.当中訪客最多的是第三课有2700的訪客. watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" > 事实上我開始的时候计划的挺好的,就依照我这阶段的安排,慢慢的带大家做一个

Android技术10:Java与C语言之间简单数据的传递

由于C语言和Java语言之间很多类型不一致性,因此使用native时,需要数据类型转换.下面演示分别传递整型,字符串,整型数组,Java静态方法传递数据. 1.创建native方法 我们单独创建一个NativeClass类来存放native方法 1 package com.forsta.ndk; 2 3 public class NativeClass { 4 public native int add(int x,int y); 5 public native String showStrin

C语言中两个相同类型的结构体变量之间是可以相互直接赋值的

C语言中,在相同类型的变量间赋值时是直接内存复制的,即将他们的内存进行复制,而两个同类型的结构体变量属于同一种变量,所以赋值时是按照他们的内存分布来直接拷贝的.所以,在C语言中两个相同类型的结构体变量之间是可以相互赋值的.但是要注意指针的浅层复制问题. 下面是一个简单的验证代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdlib.h> struct test

Android 客户端与服务端JSP相互传递中文

为了兼容简体.繁体及其他语系,推荐使用UTF-8编码. 首选,我们看看Android端应该怎么做: 在发送前,应该对参数值要进行UTF-8编码,我写了一个static的 转换函数,在做发送动作前用它将参数值转换成utf8编码: public class NetUtil { static public String toUtf8Url(String value) { try { return java.net.URLEncoder.encode(value, "utf8"); } cat

借助Intent实现Android工程中Activity之间Java对象的传递——实现Parcelable接口

借助Intent实现Android工程中Activity之间Java对象的传递有两种方式:一种所传递对象实现了Serializable接口:另一种是所传递对象实现了Parcelable接口,本博客总结传递对象实现Parcelable接口的情况下如何实现Java对象传递: 代码1.添加名为"User.java"的文件: package com.ghj.vo; import android.os.Parcel; import android.os.Parcelable; public cl

Linux CentOS7 两台机器之间免输入密码相互登录(密钥对认证)

Linux CentOS7 两台机器之间免输入密码相互登录(密钥对认证) 两台机器为: 主机名:fxq-1,IP:192.168.42.181 主机名:fxq-2, IP:192.168.42.182 w命令可以查看当前登录用户的信息 [[email protected] ~]# w  23:59:42 up 12 min,  1 user,  load average: 0.00, 0.07, 0.11USER     TTY      FROM             [email prot

借助Intent实现Android工程中Activity之间Java对象的传递——实现Serializable接口

借助Intent实现Android工程中Activity之间Java对象的传递有两种方式:一种所传递对象实现了Serializable接口:另一种是所传递对象实现了Parcelable接口,本博客总结传递对象实现Serializable接口的情况下如何实现Java对象传递: 代码1.添加名为"User.java"的文件: package com.ghj.vo; import java.io.Serializable; public class User implements Seria