至此,我们还有最后一站——消息循环
为此,当程序调试运行到CWnd::CreateEx尾部时,对GetMessage/PeekMessage下条件断点,以窗口句柄为条件:
很快就断下来了:
在AfxWinMain中,InitInstance完成后,就开始执行Run函数:
Run又是一个虚函数,CWinApp类中override了它,因此进入CWinApp::Run:
在其中又调用了CWinThread::Run,其中调用了PeekMessage以PM_NONREMOVE的方式偷看消息队列,并在空闲时执行OnIdle虚函数;如果不空闲,则进入 CWinThread::PumpMessage()函数,该函数又调用了AfxInternalPumpMessage函数:
BOOL AFXAPI AfxInternalPumpMessage()
{
_AFX_THREAD_STATE *pState = AfxGetThreadState();
if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
{
return FALSE;
}
// process this message
if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
{
::TranslateMessage(&(pState->m_msgCur));
::DispatchMessage(&(pState->m_msgCur));
}
return TRUE;
}
基本上是一个标准的消息循环了。不过注意在GetMessage后有一个AfxPreTranslateMessage,并且可以根据其返回值决定是否翻译并分发消息(如果不分发消息的话,根本就没回到user32领空,因此AfxWndProc根本就收不到该消息)
BOOL __cdecl AfxPreTranslateMessage(MSG* pMsg)
{
CWinThread *pThread = AfxGetThread();
if( pThread )
return pThread->PreTranslateMessage( pMsg );
else
return AfxInternalPreTranslateMessage( pMsg );
}
BOOL CWinThread::PreTranslateMessage(MSG* pMsg)
{
ASSERT_VALID(this);
return AfxInternalPreTranslateMessage( pMsg );
}
BOOL AfxInternalPreTranslateMessage(MSG* pMsg)
{
// ASSERT_VALID(this);
CWinThread *pThread = AfxGetThread();
if( pThread )
{
// if this is a thread-message, short-circuit this function
if (pMsg->hwnd == NULL && pThread->DispatchThreadMessageEx(pMsg))
return TRUE;
}
// walk from target to main window
CWnd* pMainWnd = AfxGetMainWnd();
if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg))
return TRUE;
// in case of modeless dialogs, last chance route through main
// window's accelerator table
if (pMainWnd != NULL)
{
CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
if (pWnd->GetTopLevelParent() != pMainWnd)
return pMainWnd->PreTranslateMessage(pMsg);
}
return FALSE; // no special processing
}
这其中,最重要的是CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg)
BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)
{
ASSERT(hWndStop == NULL || ::IsWindow(hWndStop));
ASSERT(pMsg != NULL);
// walk from the target window up to the hWndStop window checking
// if any window wants to translate this message
for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))
{
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
if (pWnd != NULL)
{
// target window is a C++ window
if (pWnd->PreTranslateMessage(pMsg))
return TRUE; // trapped by target window (eg: accelerators)
}
// got to hWndStop window without interest
if (hWnd == hWndStop)
break;
}
return FALSE; // no special processing
}
其从消息所属窗口开始,到其父窗口,一直到主窗口,以此调用其PreTranslateMessage,直到找到一个返回TRUE的,就返回TRUE,大家都不处理,则返回FALSE。这里,从hWnd到CWND*的转换又是通过CWnd::FromHandlePermanent完成的。
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
LRESULT lResult = 0;
union MessageMapFunctions mmf;
mmf.pfn = 0;
CInternalGlobalLock winMsgLock;
// special case for commands
if (message == WM_COMMAND)
{
if (OnCommand(wParam, lParam))
{
lResult = 1;
goto LReturnTrue;
}
return FALSE;
}
// special case for notifies
if (message == WM_NOTIFY)
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
goto LReturnTrue;
return FALSE;
}
// special case for activation
if (message == WM_ACTIVATE)
_AfxHandleActivate(this, wParam, CWnd::FromHandle((HWND)lParam));
// special case for set cursor HTERROR
if (message == WM_SETCURSOR &&
_AfxHandleSetCursor(this, (short)LOWORD(lParam), HIWORD(lParam)))
{
lResult = 1;
goto LReturnTrue;
}
// special case for windows that contain windowless ActiveX controls
BOOL bHandled;
bHandled = FALSE;
if ((m_pCtrlCont != NULL) && (m_pCtrlCont->m_nWindowlessControls > 0))
{
if (((message >= WM_MOUSEFIRST) && (message <= AFX_WM_MOUSELAST)) ||
((message >= WM_KEYFIRST) && (message <= WM_IME_KEYLAST)) ||
((message >= WM_IME_SETCONTEXT) && (message <= WM_IME_KEYUP)))
{
bHandled = m_pCtrlCont->HandleWindowlessMessage(message, wParam, lParam, &lResult);
}
}
if (bHandled)
{
goto LReturnTrue;
}
//否则是普通消息,垂直向上传递即可,具体代码见上
其中我们可以看到,ON_COMMAND消息被区别对待了,在CWnd类中,有一个名为OnCommand的虚函数专门处理此类消息。而CFrameWnd中重载了该函数,CView类中没有重载。因此
CFrameWnd类的对象,调用的时CFrameWnd::OnCommand,该函数沿CFrameWnd的消息映射族谱,按正常消息向上查找
BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)
// return TRUE if command invocation was attempted
{
HWND hWndCtrl = (HWND)lParam;
UINT nID = LOWORD(wParam);
CFrameWnd* pFrameWnd = GetTopLevelFrame();
ENSURE_VALID(pFrameWnd);
if (pFrameWnd->m_bHelpMode && hWndCtrl == NULL &&
nID != ID_HELP && nID != ID_DEFAULT_HELP && nID != ID_CONTEXT_HELP)
{
// route as help
if (!SendMessage(WM_COMMANDHELP, 0, HID_BASE_COMMAND+nID))
SendMessage(WM_COMMAND, ID_DEFAULT_HELP);
return TRUE;
}
// route as normal command
return CWnd::OnCommand(wParam, lParam);
}
该类中对帮助按钮所发出的COMMAND消息进行了特别处理,如果不是此类消息,则调用CWnd::OnCommand
CView类并未实现OnCommand方法,故与CWnd类的对象一样,调用的是CWnd::OnCommand。这样,除了CFrameWnd中的特殊帮助类消息外,所有ON_COMMAND消息又殊途同归了。
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
// return TRUE if command invocation was attempted
{
UINT nID = LOWORD(wParam);
HWND hWndCtrl = (HWND)lParam;
int nCode = HIWORD(wParam);
// default routing for command messages (through closure table)
if (hWndCtrl == NULL)
{
//特殊处理句柄为NULL的
}
else
{
// control notification
ASSERT(nID == 0 || ::IsWindow(hWndCtrl));
if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd)
return TRUE; // locked out - ignore control notification
// reflect notification to child window control
if (ReflectLastMsg(hWndCtrl))
return TRUE; // eaten by child
// zero IDs for normal commands are not allowed
if (nID == 0)
return FALSE;
}
return OnCmdMsg(nID, nCode, NULL, NULL);
}
关键是在最后调用了虚函数OnCmdMsg,不同类型不同的处理函数。
先看看CFrameWnd::OnCmdMsg
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
CPushRoutingFrame push(this);
// pump through current view FIRST
CView* pView = GetActiveView();
if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// then pump through frame
if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// last but not least, pump through app
CWinApp* pApp = AfxGetApp();
if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
return FALSE;
}
可以看出,框架类的OnCmdMsg依次调用了CView::OnCmdMsg、CWnd::OnCmdMsg(未override,为CCmdTarget::OnCmdMsg)
而CView::OnCmdMsg又依次调用CWnd::OnCmdMsg(未override,为CCmdTarget::OnCmdMsg)和CDocument::OnCmdMsg。其中CCmdTarget::OnCmdMsg是从CMyView->CView->CWnd->CCmdTarget的顺序找其消息映射表。
如果没找到的话,就返回,由CDocument::OnCmdMsg继续查找:CMyDoc->CDocument->CCmdTarget
如果还是没找到的话,CView::OnCmdMsg返回FALSE,由CWnd::OnCmdMsg(CCmdTarget::OnCmdMsg)接手:
CMyFrameWnd->CFrameWnd->CWnd->CCmdTarget
如果仍然没有找到,则由pApp->OnCmdMsg接手:
CMyWinApp->CWinApp->CCmdTarget
全剧终~