内存拷贝的用处之一,简单来说,就是通过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++
只要保证对方是以这个格式接收的即可,一般习惯以lpData来表示发送数据的其实指针,cbData来表示数据的大小size,dwData来表示数据发送窗口的句柄号
对于不同编码的字符,即Ansi和unicode,这两个对于到时候数据的解析是不太一样的,即发送端是以ansi格式发送的,你不能以unicode格式解析(否则乱码),同时,这两个编码格式的数据长度度量不太一样,ansi : unicode = 1:2;具体见上篇博客
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代码如下
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处理,不能少
}
见上一篇博客