本文由danny发表于 http://blog.csdn.net/danny_share
说明:建议先下载本文配套工程,其中
MessageMain工程、MessageSubA工程、MessageSubAs工程分别用于演示进程间通信的主进程和两个子进程
下载地址:http://download.csdn.net/detail/danny_share/7710705
注意:
(1)不要F5直接运行
(2)编译生成debug目录或者release目录以后,双击其中的MessageMain.exe运行
一.Windows消息机制
(1) 概念
Windows程序以消息为基础,以事件驱动(message based,event driven),这里的消息就是如下一个数据结构
typedef struct tagMSG{
HWND hwnd; //窗口句柄
UINT message;
WPARAM wParam;
LPARAM LParam;
DWORD time;//消息被传递时候的时间
POINT pt; //消息被传递时,光标位置
} MSG;
(2)原生Windows消息循环
老生常谈的话题,原生的Windows程序通过注册窗口类RegisterClass,注册时指定窗口回调函数WndProc,创建窗口CreateWindow,再使用消息循环,即可成功创建Windows程序。
其中窗口过程函数形式如下:
LRESULT CALLBACK WndProc( HWND hWnd , UINT message , WPARAM wParam , LPARAM lParam)
{
switch( message)
{
case MessageType:
{
}
Break;
}
}
消息循环形式如下:
while(GetMessag(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
(3)MFC消息循环
MFC封装了原生Windows消息循环,通过DECLEAR_MESSAGE_MAP定义了指向父类的消息映射指针,以及本身消息和消息处理的数据结构,再通过BEGIN_MESSAGE_MAP和END_MESSAGE_MAP填入刚刚定义的结构。
二.Message相关函数
(1) 消息函数
ID | 函数 | 含义 |
1 | GetMessage | 从消息队列获取消息,阻塞 |
2 | PeekMessage | 从消息队列获取消息,不管有没有消息,立即返回 |
3 | TranslateMessage | 实际是读取WM_KEYDOWN和WM_KEYUP消息 翻译产生WM_CHAR消息 |
4 | DispatchMessage | 把消息交给对应的窗口回调函数处理 |
5 | SendMessage | 把消息发送到窗口消息队列中,等其处理完成才返回 |
6 | PostMessage | 把消息发送到窗口消息队列中,立即返回 |
7 | SendDlgItemMessage | 向控件发送消息,等其处理完成才返回 |
8 | RegisterWindowMessage | 注册一个消息,成功则返回值在0xC000到0xFFFF之间 |
9 | BroadcastSystemMessage | 注册一个消息,成功则返回值在0xC000到0xFFFF之间 |
10 | BroadcastSystemMessageEx |
|
11 | GetInputState |
|
12 | GetMessageExtraInfo |
|
13 | GetMessagePos |
|
14 | GetMessageTime |
|
15 | GetQueueStatus |
|
16 | InSendMessage |
|
17 | InSendMessageEx |
|
18 | PostQuitMessage |
|
19 | PostThreadMessage | 发送消息到拥有消息队列的线程 |
20 | RegisterWindowMessage |
|
21 | ReplyMessage |
|
22 | SendAsyncProc | 传入某消息的额外的回调函数 |
23 | SendMessageCallback |
|
24 | SendMessageTimeout | 把消息发送到窗口消息队列中,等其处理完成或是超时才返回 |
25 | SendNotifyMessage | 若目标窗口由调用线程创建,则发送并等待其处理完成后返回;若目标窗口由其他线程创建,则异步发送 |
26 | SetMessageExtraInfo |
|
27 | WaitMessage | 和PeekMessage相比,在无消息时多了交出线程控制权的操作 |
(2) MFC消息宏
ID | 函数 | 含义 |
1 | DECLARE_MESSAGE_MAP | 在头文件中声明,本质是定义父类消息映射指针和自己的消息映射数据结构 |
2 | BEGIN_MESSAGE_MAP | 填充DECLARE_MESSAGE_MAP中定义的数据结构 |
3 | ON_MESSAGE | 自定义消息的映射 |
三.使用消息实现进程通信
1. 关于自定义消息
(1) 我们可能会想当然地自定义一个结构体,如下
struct MsgInfoStruct
{
char sender;
char receiver;
char command;
string data;
};
(2)再自定义一个消息比如#define WM_MSG_WORK (WM_USER+30)
(3)然后在主进程中PostMessage一个new的MsgInfoStruct,
(4) 在子进程的WM_MSG_WORK响应函数中去MsgInfoStruct* originalStruct=(MsgInfoStruct*)lParam;
(5)这样主进程PostMessage给子进程A后,可直接再PostMessage给B,相比剪贴板而言,对于子进程中耗时处理的情况,这种方式可以很好的做到让两个子进程同时运行
(6)但实际上不可行,因为两个进程拥有不同的虚拟地址空间,导致子进程不认主进程中传的地址,因此这种方式是不信的
(7)因此使用自定义消息可以发命令(通过消息来标识不同的命令),但无法传输数据
2. 关于WM_COPYDATA
(1) 由于WM_COPYDATA消息底层是通过文件映射实现的,且CreateFileMapping时共享内存名称都为”MSName”,所以为了防止数据紊乱,只能采用SendMessage而无法使用PostMessage来发送
(2) 这就和剪贴板一样,对于耗时的操作,主进程必须等一个子进程处理完成才能命令下一个子进程处理。
(3) 从这里也可以看出,WM_COPYDATA适合一个主进程和一个子进程通信的情况
下面使用WM_COPYDATA来实现进程间相互通信,COPYDATASTRUCT结构如下
struct COPYDATASTRUCT
{
DWORD dwData;//任意数据
DWORD cbData;//传输的数据长度
PVOID lpData;//cbData数据的指针
};
我们使用上篇文章设计的协议,”MA10”表示主进程M向子进程A发送命令1,数据为0
我们将数据存放在lpData中
贴上主进程M的主要代码
发送数据和命令
void CMessageMainDlg::sendDataASYNC(CWnd* WndReceive_In,int CommandType)
{
if(NULL!=WndReceive_In)
{
string content;
content+='M';
if(WndReceive_In==this->m_ASubWnd)
{
content+='A';
}
else
{
if(WndReceive_In==this->m_BSubWnd)
{
content+='B';
}
}
switch(CommandType)
{
case 1:
{
content+='1';
}
break;
case 2:
{
content+='2';
}
break;
}
content+='0';
COPYDATASTRUCT tempDataStruct;
tempDataStruct.lpData=(PVOID)(content.c_str());
tempDataStruct.cbData=content.length();
::SendMessage(WndReceive_In->m_hWnd,WM_COPYDATA,0,(LPARAM)&tempDataStruct);
}
}
响应接收代码
BOOL CMessageMainDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
if(NULL!=pCopyDataStruct)
{
string tempContent=(LPSTR)pCopyDataStruct->lpData;
tempContent=tempContent.substr(0,pCopyDataStruct->cbData);
if(tempContent.length()==4)
{
if(tempContent[1]=='M')
{
string info;
if(tempContent[0]=='A')
{
info="A sub process response Work";
}
else
{
if(tempContent[0]=='B')
{
info="B sub process response Work";
}
}
info+=tempContent[2];
MessageBox(info.c_str(),"Info",MB_OK);
}
}
}
return CDialog::OnCopyData(pWnd, pCopyDataStruct);
}
再贴上子进程A中的处理
BOOL CMessageSubADlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
CWnd* tempWnd=FindWindow(NULL,"MessageMain");
string tempContent=(LPSTR)pCopyDataStruct->lpData;
tempContent=tempContent.substr(0,pCopyDataStruct->cbData);
if(tempContent.length()==4)
{
if(tempContent[0]='M')
{
if(tempContent[1]='A')
{
string response="AM";
response+=tempContent[2];
response+=tempContent[3];
COPYDATASTRUCT copyData = { 0 };
copyData.lpData =(PVOID) response.c_str();
copyData.cbData = response.length();
::SendMessage(tempWnd->m_hWnd,WM_COPYDATA,0,LPARAM(©Data));
}
}
}
return CDialog::OnCopyData(pWnd, pCopyDataStruct);
}
详见工程
四.总结
(1)自定义消息虽然看似灵活,但由于不同进程间虚拟地址空间不同,所以只能用于传递命令,无法传递复杂结构的数据
(2)WM_COPYDATA相比于自定义消息而言,解决了无法传送复杂数据的问题,但由于其本质是共享名为”MSName”的文件映射,所以只能使用SendMessage,无法使用PostMessage,和剪贴板一样,由两个后台进程无法同时工作的缺陷
Danny
2014年8月3号
于天津河西七天酒店