消息循环.docx
- 文档编号:17053920
- 上传时间:2023-07-21
- 格式:DOCX
- 页数:13
- 大小:31.92KB
消息循环.docx
《消息循环.docx》由会员分享,可在线阅读,更多相关《消息循环.docx(13页珍藏版)》请在冰点文库上搜索。
消息循环
VC初学者入门系列之二:
消息循环
作者:
jxhnuaa(铁凌)
适用读者:
VC初学者并有C++基础。
VC初学者入门系列之一:
窗口类的诞生
一、传统SDK程序的消息循环
在传统的SDK程序中,消息循环是很简单的,也许你不信,那我们就看看下面这段代码吧:
#include
LRESULTCALLBACKWndProc(HWND,UINT,WPARAM,LPARAM);
intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,
PSTRszCmdLine,intiCmdShow)
{
staticTCHARszAppName[]=TEXT("HelloWin");
WNDCLASwndclass;
wndclass.style=CS_HREDRAW|CS_VREDRAW;
wndclass.lpfnWndProc=WndProc;
wndclass.lpszClassName=szAppName;
RegisterClass(&wndclass);
hwnd=CreateWindow(szAppName,……,NULL);
ShowWindow(hwnd,iCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
returnmsg.wParam;
}
LRESULTCALLBACKWndProc(HWNDhwnd,UINTmessage,WPARAMwParam,LPARAMlParam)
{
switch(message)
{
caseWM_CREATE:
………
caseWM_PAINT:
………
caseWM_DESTROY:
PostQuitMessage(0);
return0;
}
returnDefWindowProc(hwnd,message,wParam,lParam);
}
在WinMain中CreateWindow通过一个参数将创建的窗口和窗口类(见"窗口类的诞生"一文)联系起来,这样该窗口的所有消息都将发送到该窗口类的窗口函数WndProc,其后WndProc根据不同的消息给予不同的动作。
二、MFC期望的消息循环
在传统的SDK程序中消息循环是非常简单的,并且将窗口和窗口函数绑定在一起。
而在MFC中就出现了问题,比如CDocument类,不是窗口,所以没有窗口类,但是我也想让它响应消息,怎办?
问题不仅仅如此,我们再看看MFC的消息,就会发现更多问题。
MFC将消息分为三大类:
1.标准消息,即除WM_COMMAND之外的任何WM_开头的消息,任何派生自CWnd的类都可以接受该消息,并按照继承关系接受(如从CScrollView到CView再到CWnd)。
2.命令消息,即WM_COMMAND,任何派生自CCmdTarget的类,兼可接受该消息,接受顺序如下图所示,其中标号标注了接受消息的顺序,箭头代表调用顺序:
图1消息的拐弯流动
3.ControlNotification,通知类消息,也以WM_COMMAND形式出现,由控件产生,通知其父窗口。
三、消息宏背后的秘密
知道了MFC消息流动的要求,那MFC是怎样实现的呢?
当一个消息出现时,ApplicationFrameWork怎么知道将该消息发送给哪个对象的呢?
其实都是CCmdTarget类在作怪,所有能够接受消息的类都必须继承于CCmdTarget类,因为这些类都一个共同的特征:
含有DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP三个宏。
啊!
就这三个宏组织了一张庞大的消息映射网,也许你不信,那我们就看看这三个宏是怎样定义的:
#defineDECLARE_MESSAGE_MAP()\
private:
\
staticconstAFX_MSGMAP_ENTRY_messageEntries[];\
protected:
staticAFX_DATAconstAFX_MSGMAPmessageMap;\
virtualconstAFX_MSGMAP*GetMessageMap()const;\
#defineBEGIN_MESSAGE_MAP(theClass,baseClass)\
constAFX_MSGMAP*theClass:
:
GetMessageMap()const\
{return&theClass:
:
messageMap;}\
AFX_DATADEFconstAFX_MSGMAPtheClass:
:
messageMap=\
{&baseClass:
:
messageMap,&theClass:
:
_messageEntries[0]};\
constAFX_MSGMAP_ENTRYtheClass:
:
_messageEntries[]=\
{\
#defineEND_MESSAGE_MAP()\
{0,0,0,0,AfxSig_end,(AFX_pMSG)0}\
};\
typedefvoid(AFX_MSG_CALLCCmdTarget:
:
*AFX_PMSG)(void);
structAFX_MSGMAP_ENTRY
{
UINTnMessage;
UINTnCode;
UINTnID;
UINTnLastID;
UINTnSig;
AFX_PMSGpfn;
};
structAFX_MSGMAP
{
constAFX_MSGMAP*pBaseMap;
constAFX_MSGMAP_ENTRY*lpEntries;
};
可以看出DECLARE_MESSAGE_MAP宏在其类中申请了一个全局结构和获得该结构的函数,而在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间填写刚才的全局结构,将消息和对应的处理函数联系起来,并通过AFX_MSGMAP中的pBaseMap指针,将各类按继承顺序连接起来,从而提供消息流动的道路(即消息的直流,满足标准消息流动的要求)。
下面我们举个例子:
CMyWnd:
publicCWnd
{
……
DECLARE_MESSAGE_MAP()
}
BEGIN_MESSAGE_MAP(CMyWnd,CWnd)
ON_WM_CREATE()
ON_WM_PAINT()
END_MESSAGE_MAP()
被展开后,代码如下:
CMyWnd:
publicCWnd
{
……
private:
staticconstAFX_MSGMAP_ENTRY_messageEntries[];
protected:
staticAFX_DATAconstAFX_MSGMAPmessageMap;
virtualconstAFX_MSGMAP*GetMessageMap()const;
}
constAFX_MSGMAP*CMyWnd:
:
GetMessageMap()const
{return&CMyWnd:
:
messageMap;}
AFX_DATADEFconstAFX_MSGMAPCMyWnd:
:
messageMap=
{&CWnd:
:
messageMap,&CMyWnd:
:
_messageEntries[0]};
constAFX_MSGMAP_ENTRYCMyWnd:
:
_messageEntries[]=
{
{WM_CREATE,0,0,0,AfxSig_is,
(AFX_PMSG)(AFX_PMSGW)(int(AFX_MSG_CALLCWnd:
:
*)(LPCREATESTRUCT))OnCreate},
{WM_PAINT,0,0,0,AfxSig_vv,
(AFX_PMSG)(AFX_PMSGW)(void(AFX_MSG_CALLCWnd:
:
*)(void))OnPaint},
{0,0,0,0,AfxSig_end,(AFX_PMSG)0}
};
这样WM_CREATE,WM_PAINT在消息网中流动,当流到CMyWnd类的messageMap结构时,发现有该消息的记录,则调用记录中记载的OnCreate和OnPaint函数,进行响应消息,从而完成了Windows消息驱动机制。
四、MFC消息的起点
我们已经建立了一张消息流动网络,但是消息是怎样从产生到响应函数收到该消息,而且标准消息需要直流,命令消息还有许多拐弯(在标题二中可以看到)。
不要紧张,我们只需要看看MFC是怎样实现的。
不管怎么说,对 Windows系统来说都是一样的,它都是不断地用GetMessage(或者其它)从消息队列中取出消息,然后用DispatchMessage将消息发送到窗口函数中去。
在"窗口类的诞生"中知道,MFC将所有的窗口处理函数都注册成DefWndProc,那是不是MFC将所有的消息都发送到DefWndProc中去了呢?
很抱歉不是,而是都发送到了AfxWndProc函数去了。
你可能要问为什么,这也是我想知道的,那我们就看看MFC代码吧:
BOOLCWnd:
:
CreateEx(……)
{
……
PreCreateWindow(cs);
AfxHookWindowCreate(this);
HWNDhWnd=:
:
CreateWindowEx(……);
……
}
voidAFXAPIAfxHookWindowCreate(CWnd*pWnd)
{
……
pThreadState->m_hHookOldCbtFilter=
:
:
SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook,NULL,:
:
GetCurrentThreadId());
……
}
_AfxCbtFilterHook(intcode,WPARAMwParam,LPARAMlParam)
{
……
if(!
afxData.bWin31)
{
_AfxStandardSubclass((HWND)wParam);
}
……
}
voidAFXAPI_AfxStandardSubclass(HWNDhWnd)
{
……
oldWndProc=
(WNDPROC)SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)AfxGetAfxWndProc());
}
WNDPROCAFXAPIAfxGetAfxWndProc()
{
……
return&AfxWndProc;
}
看了上面的代码,不知你有没有了然于胸的感觉"啊,原来是这样呀!
"其实MFC在PreCreateWindow注册窗口类之后,在创建窗口之前,调用了AfxHookWindowCreate函数,该函数设置了钩子(钩子用SetWinowsHook或者SetWindowsHookEx设置,这样消息有满足设置的消息时,系统就发送给你设置的函数,这里是_AfxCbtFilterHook函数),这样每次创建窗口的时候,该函数就将窗口函数修改成AfxWndProc。
至于为什么这样做吗?
那是为了包容新的3D控件而又同MFC2.5兼容。
五、MFC消息的流动
消息的起点是AfxWndProc函数,所有的消息都被发送到AfxWndProc,也从AfxWndProc再次流向各自的消息响应函数的,怎么流的呢?
那只有MFC知道:
LRESULTCALLBACKAfxWndProc(…….)
{
……
returnAfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam);
}
LRESULTAFXAPIAfxCallWndProc(……)
{
……
lResult=pWnd->WindowProc(nMsg,wParam,lParam);
……
}
LRESULTCWnd:
:
WindowProc(……)
{
……
if(!
OnWndMsg(message,wParam,lParam,&lResult))
lResult=DefWindowProc(message,wParam,lParam);
……
}
BOOLCWnd:
:
OnWndMsg(……)//该函数原来太过庞大,被我改造了一下,只反映意思,不能执行
{
……
if(message==WM_COMMAND)
OnCommand(wParam,lParam);
if(message==WM_NOTIFY)
OnNotify(wParam,lParam,&lResult);
pMessage=GetMessageMap();
for(;pMessageMap!
=NULL;pMessageMap=pMessageMap->pBaseMap)
{
if((lpEntry=AfxFindMessageEntry(pMessageMap->lpEntries,
message,0,0))!
=NULL)
break;
}
(this->*(lpEntry->pnf))(……);//调用消息响应函数
}
AFX_MSGMAP_ENTRYAfxFindMessageEntry(……)
{
……
while(lpEntry->nSign!
=AfxSig_end)
{
if(lpEntry->nMessage==nMsg&&lpEntry->nCode==nCode&&nID>=lpEntry->nID
&&nID<=lpEntry->nLastID)
{
returnlpEntry;
}
lpEntry++;
}
……
}
消息被发送到对应窗口的OnWndMsg后,然后根据消息的类型采取相应动作:
如果是标准消息,则检查但前类中有无处理函数(由AfxFindMessageEntry实现),若没有,就在其父亲类中找(通过pMessageMap->pBaseMap实现),这样望上顺序搜索消息网,搜索结束也找不到处理函数,那么回到WindowProc函数调用默认DefWindowProc函数;如果是命令消息或通知消息则发送到OnCommand或者OnNotify函数中去处理,来实现消息的拐弯流动:
BOOLCWnd:
:
OnCommand(WPARAMwParam,LPARAMlParam)
{
……
returnOnCmdMsg(nID,nCode,NULL,NULL);
}
BOOLCFrameWnd:
:
OnCmdMsg(……)
{
CView*pView=GetActiveView();
if(pView!
=NULL&&pView->OnCmdMsg(……))//相当于图1中Frame指向View的箭头
returnTRUE;
if(CWnd:
:
OnCmdMsg(……))//图1中Frame自身
returnTRUE;
CWinApp*pApp=AfxGetApp();
if(pApp!
=NULL&&pApp->OnCmdMsg(……))//图1中CWinApp对象
returnTRUE;
returnFALSE;
}
BOOLCView:
:
OnCmdMsg(……)
{
if(CWnd:
:
OnCmdMsg(……))//图1中View本身
returnTRUE;
if(m_pDocument!
=NULL)m_pDocument->OnCmdMsg(……);//图1中View到Doc箭头
……
}
BOOLCDocument:
:
OnCmdMsg(……)
{
if(CCmdTarget:
:
OnCmdMsg(……))//图1中Doc本身
returnTRUE;
if(m_pDocTemplate!
=NULL&&m_pDocTemplate->OnCmdMsg(……))//图1中DocTemplate
returnTRUE;
returnFALSE;
}
BOOLCCmdTarget:
:
OnCmdMsg(……)//注:
CWnd没有重载CCmdTarget的OnCmdMsg
{
……
for(pMessageMap=GetMessageMap();pMessageMap!
=NULL;
pMessageMap=pMessageMap->pBaseMap)
{
lpEntry=AfxFindMessageEntry(pMessageMap->lpEntries,……);
if(lpEntry!
=NULL)
returnDispatchCmdMsg(……lpEntry->pfn,……);
}
returnFALSE;
}
从代码中可以看出,OnCmdMsg各自调用的顺序刚好就是图1中所要求的顺序,这样也就实现了消息的拐弯流动,最后DispatchCmdMsg函数是调用找到的消息处理函数处理消息。
至此消息从出现到找到处理函数已经完成!
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 消息 循环