正如剛從DOS轉到Win32下寫程序時,總是爲找不到main函數而感到不爽,學習時WTL時,第一反應是找GetMessage和DispatchMessage,想知道消息是如何分發到窗口的。
在_tWinMain裏做了一些初始化函數之後,就進入了Run函數,顯然Run函數就是消息循環。下面是Run函數的代碼:
int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT) { CMessageLoop theLoop; _Module.AddMessageLoop(&theLoop);
CMainFrame wndMain;
if(wndMain.CreateEx() == NULL) { ATLTRACE(_T("Main window creation failed!/n")); return 0; }
wndMain.ShowWindow(nCmdShow);
int nRet = theLoop.Run();
_Module.RemoveMessageLoop(); return nRet; } |
不出所料,CmessageLoop就是用作消息循環的類,_Module.AddMessageLoop不過是保存一個全局引用,其它地方可以方便的取得CmessageLoop的實例theLoop。
CmessageLoop::Run函數無疑就是消息循環函數了:
int Run() { BOOL bDoIdle = TRUE; int nIdleCount = 0; BOOL bRet;
for(;;) { while(bDoIdle && !::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE)) { if(!OnIdle(nIdleCount++)) bDoIdle = FALSE; }
bRet = ::GetMessage(&m_msg, NULL, 0, 0);
if(bRet == -1) { ATLTRACE2(atlTraceUI, 0, _T("::GetMessage returned -1 (error)/n")); continue; // error, don't process } else if(!bRet) { ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting/n")); break; // WM_QUIT, exit message loop }
if(!PreTranslateMessage(&m_msg)) { ::TranslateMessage(&m_msg); ::DispatchMessage(&m_msg); }
if(IsIdleMessage(&m_msg)) { bDoIdle = TRUE; nIdleCount = 0; } }
return (int)m_msg.wParam; } |
CmessageLoop::Run函數,不斷的從消息隊列裏取消息,然後分發給對應的窗口。當消息分發到窗口時,自然就是調用窗口的WINPROC函數了。那麼WINPROC是在哪裏註冊,在哪裏實現的呢?
HWND CwindowImpl::Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL, DWORD dwStyle = 0, DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL) { if (T::GetWndClassInfo().m_lpszOrigName == NULL) T::GetWndClassInfo().m_lpszOrigName = GetWndClassName(); ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);
dwStyle = T::GetWndStyle(dwStyle); dwExStyle = T::GetWndExStyle(dwExStyle);
return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rcPos, szWindowName, dwStyle, dwExStyle, nID, atom, lpCreateParam); }
|
原來在窗口創建函數裏註冊WinClass,因爲這是在基類裏實現的,它需要從子類窗口中獲得WinClass信息。很明顯,這裏調用GetWndClassInfo獲得WinClass信息。但是我們並沒有看到子類裏實現該函數,應該是宏展開的吧,DECLARE_XXXX_WND_CLASS之類最爲可疑:
#define DECLARE_FRAME_WND_CLASS(WndClassName, uCommonResourceID) / static CFrameWndClassInfo& GetWndClassInfo() / { / static CFrameWndClassInfo wc = / { / { 0, StartWindowProc, / 0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName }, / NULL, NULL, IDC_ARROW, TRUE, 0, _T(""), uCommonResourceID / }; / return wc; / }
|
沒錯,就是它,原來WINPROC函數名爲StartWindowProc。問題又來了:StartWindowProc在哪裏實現了,子類沒有,自然是基類裏了:
template <class TBase, class TWinTraits> LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_Module.ExtractCreateWndData(); ATLASSERT(pThis != NULL); pThis->m_hWnd = hWnd; pThis->m_thunk.Init(pThis->GetWindowProc(), pThis); WNDPROC pProc = (WNDPROC)&(pThis->m_thunk.thunk); WNDPROC pOldProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc); #ifdef _DEBUG // check if somebody has subclassed us already since we discard it if(pOldProc != StartWindowProc) ATLTRACE2(atlTraceWindowing, 0, _T("Subclassing through a hook discarded./n")); #else pOldProc; // avoid unused warning #endif return pProc(hWnd, uMsg, wParam, lParam); } |
這個函數的實現有點晦澀,主要是不太明白thunk指的是什麼。基本功能倒是很明顯,只是重新設置了一下WINPROC,也就是說WINPROC被換成新的了,下次再也不會調用StartWindowProc了。很快找到了真正的WINPROC:
template <class TBase, class TWinTraits> LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd; // set a ptr to this message and save the old value MSG msg = { pThis->m_hWnd, uMsg, wParam, lParam, 0, { 0, 0 } }; const MSG* pOldMsg = pThis->m_pCurrentMsg; pThis->m_pCurrentMsg = &msg; // pass to the message map to process LRESULT lRes; BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0); // restore saved value for the current message ATLASSERT(pThis->m_pCurrentMsg == &msg); pThis->m_pCurrentMsg = pOldMsg; // do the default processing if message was not handled if(!bRet) { if(uMsg != WM_NCDESTROY) lRes = pThis->DefWindowProc(uMsg, wParam, lParam); else { // unsubclass, if needed LONG pfnWndProc = ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC); lRes = pThis->DefWindowProc(uMsg, wParam, lParam); if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC) == pfnWndProc) ::SetWindowLong(pThis->m_hWnd, GWL_WNDPROC, (LONG)pThis->m_pfnSuperWindowProc); // clear out window handle HWND hWnd = pThis->m_hWnd; pThis->m_hWnd = NULL; // clean up after window is destroyed pThis->OnFinalMessage(hWnd); } } return lRes; } |
在這個函數裏,調用ProcessWindowMessage去分發消息,ProcessWindowMessage是在哪裏實現的呢?沒有找到自然是宏展開的:
#define BEGIN_MSG_MAP(theClass) / public: / BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) / { / BOOL bHandled = TRUE; / hWnd; / uMsg; / wParam; / lParam; / lResult; / bHandled; / switch(dwMsgMapID) / { / #define MESSAGE_HANDLER(msg, func) / if(uMsg == msg) / { / bHandled = TRUE; / lResult = func(uMsg, wParam, lParam, bHandled); / if(bHandled) / return TRUE; / } #define END_MSG_MAP() / break; / default: / ATLTRACE2(atlTraceWindowing, 0, _T("Invalid message map ID (%i)/n"), dwMsgMapID); / ATLASSERT(FALSE); / break; / } / return FALSE; / }
|
原來BEGIN_MSG_MAP等函數就是用展開ProcessWindowMessage函數的,而ProcessWindowMessage又是爲用來實現WinProc函數的,這下消息的流動過程就很清楚了。
另外與消息處理函數的兩個類是CmessageFilter和CidleHandler,兩者都是接口類,各定義了一個純虛函數。在子類裏實現它們,然後註冊到CMessageLoop中去,CMessageLoop會在適當的時候調用它們。前者一般來轉換快捷鍵,後者一般用來更新界面。