C++进程间通信之SendMessage和PostMessage
C++进程间通信之SendMessage和PostMessage
SendMessage和PostMessage在Win32编程中是很常见的,主要是⽤来发送消息到指定的窗⼝,⼀般⽤于⼯作线程传输数据到UI线程。其中SendMessage函数将指定的消息发送到⼀个或多个窗⼝。此函数为指定的窗⼝调⽤窗⼝程序,直到窗⼝程序处理完消息再返回。⽽函数PostMessage不同,将⼀个消息寄送到⼀个线程的消息队列后⽴即返回。
同样的,对于多个Win32进程,只要它有窗⼝,我们也可以通过SendMessage和PostMessage来实现两个进程间的数据通信。进程内传输时,由于在同⼀个进程中,所有数据都是存在于相同的虚拟内存空间中,可以通过传输数据地址直接达到数据传输的⽬的。⽽不同两个进程,它们是两个独⽴的虚拟内存空间,同⼀地址对不同的进程来说并不⼀定指向同⼀物理内存,内容也就不⼀定⼀样,因此不同进程⽆法通过传地址的⽅式传递字符串。下⾯分别介绍SendMessage和PostMessage在跨进程传输数据时的使⽤。
1,使⽤介绍
(⼀) PostMessage
PostMessage发送时只需要把消息丢到窗⼝所属线程的消息队列中就会⽴即返回,不会阻塞发送端线程,
但是在多线程中由于使⽤不同的虚拟地址空间,不能进⾏寻址操作,因此不能直接发送数据。换⼀种思路,我们可以为⾃⼰的业务定义不同的多种消息,不同消息代表不同的含义:
#define WM_WORK1_START WM_USER+1000
#define WM_WORK2_STOP WM_USER+1001
#define WM_WORK2_START WM_USER+1002
#define WM_WORK2_ STOP WM_USER+1003
发送消息通知后直接返回,由服务端接收不同的消息id进⾏业务处理,这种⽅式处
理起来⽐较简陋,但是也可以实现基本命令传输的⽬的。这⾥可能会有⼈提到WM_COPYDATA消息,它可以很好地传输我们需要交互的数据,但是由于系统必须管理⽤以传递数据的缓冲区的⽣命期,如果使⽤了PostMessage(),数据缓冲区会在接收⽅(线程)有机会处理该数据之前,就被系统清除和回收。
(⼆) SendMessage
SendMessage是⼀种阻塞的消息模式,它会等待消息处理结果返回才执⾏完毕。那
么这⾥我们就可以通过WM_COPYDATA来进⾏字符串数据的传输。WM_COPYDATA主要⽤到⼀个结构体COPYDATASTRUCT,定义如下:
typedef struct tagCOPYDATASTRUCT {
ULONG_PTR dwData;
DWORD cbData;
_Field_size_bytes_(cbData) PVOID lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;
其中dwData为32位的⾃定义数据, lpData为指向数据的指针,cbData为lpData指针指向数据的⼤⼩(字节数)。
我们需要定义⼀个结构体来进⾏数据的传输,⽐如:
struct tagMESSAGE
{
char szMsg[256];
};
⼜看到了熟悉的字符串操作,填充需要传输的字符串数据,lpData也就是该结构体的指针,⼤概代码如下:
tagMESSAGE msg;
memset(&msg, 0, sizeof(tagMESSAGE));
strncpy(msg.szMsg, (*it).c_str(), sizeof(msg.szMsg) - 1);
COPYDATASTRUCT cpd;
cpd.cbData = sizeof(tagMESSAGE) + 1;
cpd.lpData = (PVOID)&msg;
cpd.dwData = (DWORD)m_hLogWnd;
进程通信方式SendMessage(m_hLogWnd, WM_COPYDATA, (WPARAM)g_hWnd, (LPARAM)&cpd);
在接收端(m_hLogWnd),响应WM_COPYDATA消息即可。
BOOL xxxDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
// TODO: 在此添加消息处理程序代码和/或调⽤默认值
tagMESSAGE* pMsg = (struct tagMESSAGE *)pCopyDataStruct->lpData;
if (NULL == pMsg)
{
return CDialog::OnCopyData(pWnd, pCopyDataStruct);
}
//判断参数⼤⼩
DWORD dwCbd = pCopyDataStruct->cbData;
if (dwCbd != sizeof(tagMESSAGE) + 1)
{
return CDialog::OnCopyData(pWnd, pCopyDataStruct);
}
ShowLog(pMsg->szMsg);
return CDialog::OnCopyData(pWnd, pCopyDataStruct);
}
2,需要注意的地⽅
在我的项⽬中,A进程是主程序,B进程是⼀个管理客户端,对于A程序中的重要的⽇志,需要实时显⽰到B程序上,在某些条件下,⽇志刷新是⽐较频繁的。在调试这两个进程通过SendMessage发送WM_COPYDATA时,发现⽇志内容存在⼤量丢失的情况,这⾥将我对这个问题分析得到的经验跟⼤家⼀起分享下。
(1) 发送端(A进程)发送WM_COPYDATE操作⽐较频繁时,需要新建⼀个队列来存储需要发送的数据,开启⼀个新的⼯作线程遍历发送该数据队列,这样可以避免发送端数据漏发。
(2) 接收端(B进程)接收WM_COPYDATA时,如果业务处理⽐较耗时,⽐如需要写⽂件等操作时,也需要新建⼀个队列来存储接收的数据,开启⼀个新的⼯作线程去处理该接收队列,避免接收端耗时。
3,WM_COPYDATA的原理
WM_COPYDATA是⽤内存映射机制实现的。发送进程为A,⽬标进程为B。当A进程⽤
SendMessage(WM_COPYDATA,wParam,lParam)进⾏发送的时候,它先调⽤
HANDLE hMap = CreateFileMapping(0xFFFFFFFF,NULL,PAGE_READWRITE,NULL,dwSize,"MSName")
创建⼀个共享内存⽂件, 其中dwSize为lParam信息得到的长度(也就是COPYDATASTRUCT结构得到的信息),MSName我理解是操作系统为WM_COPYDATA消息使⽤共享内存时特意定义的共享内存的名称。再通过MapViewOfFile()将地址映射到A进程中,A进程调⽤SendMessage时,将数据写⼊到该地址中。
当B进程处理WM_COPYDATA消息时,它先⽤OpenFileMapping()打开A创建的共享内存⽂件,也通过MapViewOfFile()将地址映射到B进程中,直接读取对应的数据。
由于使⽤共享内存机制,每次SendMessage时,都会覆盖上⼀次的数据,所以需要如果A进程发送频繁,那么中间就会存在数据丢失的情况。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论