您的位置:首页 > 理论基础 > 数据结构算法

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

2016-07-18 16:01 591 查看
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/

     也就是它给了我灵感,再次谢谢翻译这篇博客的兄弟!


[b]<二>C#端发送与接收:(如果在与C++通信时,请格外注意)
[/b]

以下我只贴关键实例代码,完整的请参照上面那篇博客和这篇http://www.cnblogs.com/sbCat/p/5257521.html

[b]          1. 发送:
[/b]



//这里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蛋都碎了,各种千篇一律,文不对题,模棱两可,错的也乱帖,越看越傻逼!哎,苦逼了我们这些新手,所以才下决心把我整个流程的思绪整理下,分享给大家看看,水平有限,见笑了。
        写这篇博客吐槽了很多,总之在寻求真理的路上也收获颇多,希望与大家一同进步!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: