内存拷贝(SendMessage)在C#、C++、QT之间的互相通讯实现

目录

  • 前情提要
    • 什么是内存拷贝
  • 代码
    • C++与C#通讯
    • C#和QT之间的通讯
    • QT和C++之间的通讯
    • 本文的所有上述功能皆已在windows下进行测试过,可放心食用
      • 如果上文中叙述的观点及内容有与您理解出入的地方,欢迎交流

前情提要

什么是内存拷贝

内存拷贝的用处之一,简单来说,就是通过Windows的宏SendMessage来进行本机台上不同软件的通讯;不同于串口,网口等通讯,需要同硬件设备进行交互(虚拟串口,虚拟网口除外,不过这样更麻烦);

而内存拷贝可以让进入windows进程消息循环的两个应用进行信息交互;如何交互呢,举个例子,当你在键盘上按下一个按键,此时windows接收到一个消息,又或者你用鼠标在屏幕上点击了一下,其中,区分这些的标志是一个叫做消息号的东西
部分消息号如下所示。

//0x0001——0x0087  主要是窗口消息
//0x00A0——0x00A9  非客户区消息
//0x0100——0x0108  键盘消息
//0x0111——0x0126  菜单蹋消息
//0x0132——0x0138  颜色控制消息
//0x0200——0x020A  鼠标消息
//0x0211——0x0213  菜单循环消息
//0x0220——0x0230  多文档消息
//0x03E0——0x03E8  DDE消息
//0x0400  WM_USER
//0x0400——0x7FFF  自定义消息

我们一般在进行内存拷贝通讯的时候,使用的消息号是0400或004A,不固定,需要同要进行通讯的双方软件定义一致,双方使用的消息号要相同,才能进行数据收发。

有了消息号,还需要一个发送目标来指定接收方,内存拷贝使用的是窗口的标题,即软件窗口的标题,在windows进程中对该软件的命名也是根据窗口标题来的,有了这个,就可以准确无误的数据交互了。

代码

说到不同语言的内存拷贝通讯,是有点差别的,下面我们就来说说具体细节
相同语言(通讯双方软件的开发语言)的内存拷贝代码上无任何不同,这里不做赘述

传输数据的格式,一般可以只穿字符串,也可以传一个结构体,但是无论是那种数据,都要以指针的形式传递
下面我们使用结构体,关于结构体的定义,不同平台之间的定义相去无几
如c++
内存拷贝(SendMessage)在C#、C++、QT之间的互相通讯实现_第1张图片
只要保证对方是以这个格式接收的即可,一般习惯以lpData来表示发送数据的其实指针,cbData来表示数据的大小size,dwData来表示数据发送窗口的句柄号

对于不同编码的字符,即Ansi和unicode,这两个对于到时候数据的解析是不太一样的,即发送端是以ansi格式发送的,你不能以unicode格式解析(否则乱码),同时,这两个编码格式的数据长度度量不太一样,ansi : unicode = 1:2;具体见上篇博客

C++与C#通讯

C++这边的接收和发送

//本应用对于windows的消息循环重载函数,即接收数据处理的地方
LRESULT CTestCommunicationDlg::OnCopyData(WPARAM wParam,LPARAM lParam)
{
	PCOPYDATASTRUCT pcds = (PCOPYDATASTRUCT)lParam;
	m_strDataRcv = (LPCWSTR)(pcds->lpData);
	GetDlgItem(IDC_EDIT2)->SetWindowTextW(m_strDataRcv);
	return 1L;
}

BOOL CTestCommunicationDlg::SendDataByWmCopyData(CString strText)
{
	CWnd *pDisplayWnd = CWnd::FindWindow(NULL, _T("内存拷贝通讯"));

	if(pDisplayWnd == NULL){
		AfxMessageBox(_T("没有找到内存拷贝通讯窗口"));
		return FALSE;
	}
	COPYDATASTRUCT cds;
	memset(&cds, 0, sizeof(cds));
	cds.dwData = 0;

	//发送unicode字符,大小等于字串的size*2,sizeof(TCHAR)=2
	cds.cbData = (strText.GetLength() ) * 2 + 1; // +1 for the NULL
	USES_CONVERSION;
	//cds.lpData = (LPVOID)W2A(strText);//此处为发送ansi字符
	cds.lpData = (LPVOID)(LPCWSTR)(strText);

	::SendMessage(pDisplayWnd->GetSafeHwnd(), WM_COPYDATA,
		(WPARAM)0, (LPARAM)&cds);

	return TRUE;
}

c#这边的接收和发送
定义windows接口api和数据结构声明

[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
    public IntPtr dwData;//用户定义数据
    public int cbData;//用户定义数据的长度
    
    public IntPtr lpData;
}
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern IntPtr SendMessage(int hWnd, int msg, IntPtr wParam, IntPtr lParam); //发送消息函数。

[DllImport("User32.dll", EntryPoint = "FindWindow")]
private static extern int FindWindow(string lpClassName, string lpWindowName);//查找窗口函数

接收和发送

private void buttonSend_Click(object sender, EventArgs e)
{
	    WINDOW_HANDLER = FindWindow(null, textBoxTargetWindName.Text);//通过窗口标题,获得句柄
	
    IntPtr p = Marshal.StringToHGlobalUni(textBoxSend.Text);//unicode
    //IntPtr p = Marshal.StringToHGlobalAnsi(textBoxSend.Text);//ansi
    int size=textBoxSend.Text.Length*2+1;//发送数据大小,即原始数据的大小,而不是指针的大小//unicode
    //int size=textBoxSend.Text.Length*1+1;//发送数据大小,即原始数据的大小,而不是指针的大小,ansi

    COPYDATASTRUCT cOPYDATASTRUCT = new COPYDATASTRUCT();
    cOPYDATASTRUCT.cbData = size;
    cOPYDATASTRUCT.dwData = (IntPtr)0;
    cOPYDATASTRUCT.lpData = p;

    IntPtr iPtr = Marshal.AllocHGlobal(Marshal.SizeOf(cOPYDATASTRUCT));
    Marshal.StructureToPtr(cOPYDATASTRUCT, iPtr, true);

    COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(iPtr, typeof(COPYDATASTRUCT));

    //最终的发送
    SendMessage(WINDOW_HANDLER, 0x004A, IntPtr.Zero, iPtr);
}

//重载本应用的windows消息循环处理函数
protected override void WndProc(ref Message m)   //重新函数,处理接收的信息
{
    switch (m.Msg)
    {
        case WM_USER://消息号0400
            str = Marshal.PtrToStringAnsi(m.WParam);
            textBoxRecv.Text += "接收:" + str + "\r\n";
            break;
        case 0x004A://消息号
            COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure((IntPtr)m.LParam, typeof(COPYDATASTRUCT));
            str = Marshal.PtrToStringUni(cds.lpData); ;
            //str = Marshal.PtrToStringAnsi(cds.lpData); ;
            textBoxRecv.Text += "接收:" + str.Replace("\0", "") + "\r\n";
            break;
        default: break;
    }
    base.WndProc(ref m);
}

C#和QT之间的通讯

C#同上
QT代码如下

void MainWindow::on_btMemSend_clicked()
{
    m_MemTargetWin = ::FindWindow(NULL, ui->tbMemTargerWinName->text().toStdWString().c_str());//通过主窗口类名寻找主窗口句柄

    WId wid = this->winId(); //这个窗口的winid
    if (NULL != m_MemTargetWin)
    {
        std::thread th([=](){ //单独启动一个线程进行数据传递
            QString command = ui->tbMemSend->toPlainText();//传递的内容

            COPYDATASTRUCT data;    //使用COPYDATA的方式进行数据传递
            data.dwData = 0;
            data.cbData = command.length()*2+1;//unicode size = length*2+1;ansi size=length
            data.lpData = (LPVOID)command.toStdWString().data();
            ::SendMessage(m_MemTargetWin, WM_COPYDATA, (WPARAM)wid, (LPARAM)&data);
        });

        th.detach();//传递结束后,进行关闭线程
    }
}

//本应用的windows消息重载,要在头文件中声明重载
bool MainWindow::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
    if (eventType == "windows_generic_MSG") //windows平台
    {
        MSG* msg = reinterpret_cast<MSG*>(message);
        if (msg->message == WM_APPCOMMAND || msg->message == WM_COPYDATA)//消息类型
        {
            COPYDATASTRUCT *data ;
            data = reinterpret_cast<COPYDATASTRUCT*>(msg->lParam);
            QString str=QString::fromStdWString((LPCWSTR)(data->lpData));//unicode过来用宽字符接收,LPCWSTR、LPCSTR或其他根据发送端而定
            //QString str=QString::fromStdString((char*)(data->lpData));//ansi过来用默认字符char*接收,如果是unicode过来的用ansi接收,则只能接收到第一个字符
            // int size2 = sizeof(&data);
            //char * dest = (char*)malloc(sizeof (char)*(int)data->cbData);
            //memcpy(dest,data->lpData,(int)data->cbData);//只能接收到第一个字符

            //QTextCodec *codec = QTextCodec::codecForName("UTF-8");
            //QTextCodec::setCodecForLocale(codec);

            //char * a=(char *)(data->lpData);//只能接收到第一个字符

            //QString recevice = codec->toUnicode(a);//转码
            //QString recevice((char *)(data->lpData));//只能接收到第一个字符

            ui->tbMemRecv->append(str);
        }
    }
    return QWidget::nativeEvent(eventType, message, result);//交给Qt处理,不能少
}

QT和C++之间的通讯

见上一篇博客

本文的所有上述功能皆已在windows下进行测试过,可放心食用

如果上文中叙述的观点及内容有与您理解出入的地方,欢迎交流

你可能感兴趣的:(C#,qt,上位机,c#,c++,qt,人工智能)