您的位置:首页 > 运维架构

windows进程通信之消息和WM_COPYDATA

2014-08-03 22:33 489 查看
本文由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号
于天津河西七天酒店
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: