分类: C/C++
2008-04-23 21:46:04
VC初学者入门系列之二:消息循环
作者:
适用读者:VC初学者并有C 基础。
VC初学者入门系列之一:窗口类的诞生
一、传统SDK程序的消息循环
在传统的SDK程序中,消息循环是很简单的,也许你不信,那我们就看看下面这段代码吧:
#include在WinMain 中 CreateWindow通过一个参数将创建的窗口和窗口类(见"窗口类的诞生"一文)联系起来,这样该窗口的所有消息都将发送到该窗口类的窗口函数WndProc,其后WndProc根据不同的消息给予不同的动作。LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("HelloWin") ; WNDCLAS wndclass ; 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) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: ……… case WM_PAINT: ……… case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
#define DECLARE_MESSAGE_MAP()\ private:\ static const AFX_MSGMAP_ENTRY _messageEntries[];\ protected: static AFX_DATA const AFX_MSGMAP messageMap;\ virtual const AFX_MSGMAP* GetMessageMap() const;\ #define BEGIN_MESSAGE_MAP(theClass, baseClass)\ const AFX_MSGMAP* theClass::GetMessageMap() const\ {return &theClass::messageMap;}\ AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \ {&baseClass::messageMap, &theClass::_messageEntries[0]};\ const AFX_MSGMAP_ENTRY theClass::_messageEntries[]=\ {\ #define END_MESSAGE_MAP()\ {0,0,0,0,AfxSig_end,(AFX_pMSG)0}\ };\ typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void); struct AFX_MSGMAP_ENTRY { UINT nMessage; UINT nCode; UINT nID; UINT nLastID; UINT nSig; AFX_PMSG pfn; }; struct AFX_MSGMAP { const AFX_MSGMAP* pBaseMap; const AFX_MSGMAP_ENTRY* lpEntries; };可以看出DECLARE_MESSAGE_MAP宏在其类中申请了一个全局结构和获得该结构的函数,而在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间填写刚才的全局结构,将消息和对应的处理函数联系起来,并通过AFX_MSGMAP中的pBaseMap指针,将各类按继承顺序连接起来,从而提供消息流动的道路(即消息的直流,满足标准消息流动的要求)。
CMyWnd : public CWnd { …… DECLARE_MESSAGE_MAP() } BEGIN_MESSAGE_MAP(CMyWnd,CWnd) ON_WM_CREATE() ON_WM_PAINT() END_MESSAGE_MAP()被展开后,代码如下:
CMyWnd:public CWnd { …… private: static const AFX_MSGMAP_ENTRY _messageEntries[]; protected: static AFX_DATA const AFX_MSGMAP messageMap; virtual const AFX_MSGMAP* GetMessageMap() const; } const AFX_MSGMAP* CMyWnd::GetMessageMap() const { return &CMyWnd::messageMap;} AFX_DATADEF const AFX_MSGMAP CMyWnd::messageMap= {&CWnd::messageMap, &CMyWnd::_messageEntries[0]}; const AFX_MSGMAP_ENTRY CMyWnd::_messageEntries[]= { {WM_CREATE,0,0,0,AfxSig_is, (AFX_PMSG)(AFX_PMSGW)(int(AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT))OnCreate}, {WM_PAINT,0,0,0,AfxSig_vv, (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))OnPaint}, {0,0,0,0,AfxSig_end,(AFX_PMSG)0} };这样 WM_CREATE,WM_PAINT 在消息网中流动,当流到CMyWnd类的 messageMap 结构时,发现有该消息的记录,则调用记录中记载的 OnCreate 和 OnPaint 函数,进行响应消息,从而完成了 Windows 消息驱动机制。
BOOL CWnd::CreateEx(……) { …… PreCreateWindow(cs); AfxHookWindowCreate(this); HWND hWnd = ::CreateWindowEx(……); …… } void AFXAPI AfxHookWindowCreate(CWnd *pWnd) { …… pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook,NULL,::GetCurrentThreadId()); …… } _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam) { …… if(!afxData.bWin31) { _AfxStandardSubclass((HWND)wParam); } …… } void AFXAPI _AfxStandardSubclass(HWND hWnd) { …… oldWndProc = (WNDPROC)SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)AfxGetAfxWndProc()); } WNDPROC AFXAPI AfxGetAfxWndProc() { …… return &AfxWndProc; }看了上面的代码,不知你有没有了然于胸的感觉"啊,原来是这样呀!"其实MFC在PreCreateWindow注册窗口类之后,在创建窗口之前,调用了AfxHookWindowCreate函数,该函数设置了钩子(钩子用SetWinowsHook或者SetWindowsHookEx设置,这样消息有满足设置的消息时,系统就发送给你设置的函数,这里是_AfxCbtFilterHook函数),这样每次创建窗口的时候,该函数就将窗口函数修改成AfxWndProc。至于为什么这样做吗?那是为了包容新的3D控件而又同MFC2.5兼容。
LRESULT CALLBACK AfxWndProc(…….) { …… return AfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam); } LRESULT AFXAPI AfxCallWndProc(……) { …… lResult = pWnd->WindowProc(nMsg,wParam,lParam); …… } LRESULT CWnd::WindowProc(……) { …… if(!OnWndMsg(message,wParam,lParam,&lResult)) lResult = DefWindowProc(message,wParam,lParam); …… } BOOL CWnd::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_ENTRY AfxFindMessageEntry(……) { …… while(lpEntry->nSign!=AfxSig_end) { if(lpEntry->nMessage==nMsg&&lpEntry->nCode==nCode&&nID>=lpEntry->nID &&nID<=lpEntry->nLastID) { return lpEntry; } lpEntry ; } …… }消息被发送到对应窗口的OnWndMsg后,然后根据消息的类型采取相应动作:如果是标准消息,则检查但前类中有无处理函数(由AfxFindMessageEntry实现),若没有,就在其父亲类中找(通过pMessageMap->pBaseMap实现),这样望上顺序搜索消息网,搜索结束也找不到处理函数,那么回到WindowProc函数调用默认DefWindowProc函数;如果是命令消息或通知消息则发送到OnCommand或者OnNotify函数中去处理,来实现消息的拐弯流动:
BOOL CWnd::OnCommand(WPARAM wParam,LPARAM lParam) { …… return OnCmdMsg(nID,nCode,NULL,NULL); } BOOL CFrameWnd::OnCmdMsg(……) { CView* pView = GetActiveView(); if(pView!=NULL&&pView->OnCmdMsg(……)) //相当于图1中Frame指向View的箭头 return TRUE; if(CWnd::OnCmdMsg(……)) //图1中Frame自身 return TRUE; CWinApp *pApp = AfxGetApp(); if(pApp != NULL && pApp->OnCmdMsg(……)) //图1中CWinApp对象 return TRUE; return FALSE; } BOOL CView::OnCmdMsg(……) { if(CWnd::OnCmdMsg(……)) //图1中View本身 return TRUE; if(m_pDocument!=NULL) m_pDocument->OnCmdMsg(……);//图1中View到Doc箭头 …… } BOOL CDocument::OnCmdMsg(……) { if(CCmdTarget::OnCmdMsg(……)) //图1中Doc本身 return TRUE; if(m_pDocTemplate!=NULL&&m_pDocTemplate->OnCmdMsg(……))//图1中Doc Template return TRUE; return FALSE; } BOOL CCmdTarget::OnCmdMsg(……)//注:CWnd没有重载CCmdTarget的OnCmdMsg { …… for(pMessageMap=GetMessageMap();pMessageMap!=NULL; pMessageMap=pMessageMap->pBaseMap) { lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,……); if(lpEntry!=NULL) return DispatchCmdMsg(……lpEntry->pfn,……); } return FALSE; }从代码中可以看出,OnCmdMsg各自调用的顺序刚好就是图1中所要求的顺序,这样也就实现了消息的拐弯流动,最后DispatchCmdMsg 函数是调用找到的消息处理函数处理消息。至此消息从出现到找到处理函数已经完成!