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进程发送频繁,那么中间就会存在数据丢失的情况。

发布了78 篇原创文章 · 获赞 79 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/bajianxiaofendui/article/details/89632963