WTL流程分析-初稿

WTL流程分析

歡迎訪問我的個人主頁http://www.noasia.net/taowen

一個窗口從創建到銷燬,有這麼幾個主要過程。

winmain

  • 註冊窗口類
  • 創建窗口
  • 進入消息循環

wndproc

  • 處理消息

現在我們就是要挖掘出wtl中在何處處理這些東西,怎麼處理的。首先:

winmain在哪裏?

winmain在和工程名相同的cpp文件中。名字叫做_twinmain

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)

{

       HRESULT hRes = ::CoInitialize(NULL);

// If you are running on NT 4.0 or higher you can use the following call instead to

// make the EXE free threaded. This means that calls come in on a random RPC thread.

//     HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);

       ATLASSERT(SUCCEEDED(hRes));

       // this resolves ATL window thunking problem when Microsoft Layer for Unicode (MSLU) is used

       ::DefWindowProc(NULL, 0, 0, 0L);

       AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES);  // add flags to support other controls

       hRes = _Module.Init(NULL, hInstance);

       ATLASSERT(SUCCEEDED(hRes));

       int nRet = Run(lpstrCmdLine, nCmdShow);

       _Module.Term();

       ::CoUninitialize();

       return nRet;

}

從這個函數中,看不出什麼,基本上實質上的內容都被分配在別的函數中處理了。這裏所說的別的函數就是Run(lpstrCmdLine, nCmdShow);這個函數是我們自己寫的,就在這個_twinmain的上面。

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;

}

從名字MessageLoopCreateEx就可以猜測到這個Run就是創建窗口並進入消息循環的地方。所以

winmain進行必要的初始化,主要的工作在Run中進行

Run創建窗口並進入消息循環。

窗口的創建

很容易就可以知道這麼一段完成了窗口的創建

       CMainFrame wndMain;

       if(wndMain.CreateEx() == NULL)

       {

              ATLTRACE(_T("Main window creation failed!/n"));

              return 0;

       }

CMainFrame定義在MainFrm.h中。

class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>,         public CMessageFilter, public CIdleHandler

可見這裏使用了多繼承,這是一個普遍行爲。主要繼承於CFrameWindowImpl,而且這個是模板,提供的參數就是CMainFrame。後面可以發現,這個參數在基類中用於強制類型轉換,算是向下轉換。

創建調用的是wndMain.CreateEx(),這個函數在CMainFrame中找不到,自然在其基類中有。這個是CFrameWindowImpl中的CreateEx()

       HWND CreateEx(HWND hWndParent = NULL, _U_RECT rect = NULL, DWORD dwStyle = 0, DWORD dwExStyle = 0, LPVOID lpCreateParam = NULL)

       {

              TCHAR szWindowName[256];

              szWindowName[0] = 0;

              ::LoadString(_Module.GetResourceInstance(), T::GetWndClassInfo().m_uCommonResourceID, szWindowName, 256);

              HMENU  hMenu=::LoadMenu(_Module.GetResourceInstance(), MAKEINTRESOURCE(T::GetWndClassInfo().m_uCommonResourceID));

              T* pT = static_cast<T*>(this);

              HWND hWnd = pT->Create(hWndParent, rect, szWindowName, dwStyle, dwExStyle, hMenu, lpCreateParam);

              if(hWnd!=NULL)

                     m_hAccel=::LoadAccelerators(_Module.GetResourceInstance(),MAKEINTRESOURCE(T::GetWndClassInfo().m_uCommonResourceID));

              return hWnd;

       }

等等,我們在這裏發現了一個奇異的行爲。

T* pT = static_cast<T*>(this);

這是什麼,強制類型轉換,而且是基於模板參數的類型轉換。嗯,這個就是ATL開發組偶然發明的仿真動態綁定。利用給基類提供派生類作爲模板參數,在函數調用的時候強制類型轉換以在編譯期間決定調用是哪個函數。這樣作使得我們可以在派生類中改寫基類中的函數,並且免去了虛函數帶來的代價。所以說

pT->Create(hWndParent, rect, szWindowName, dwStyle, dwExStyle, hMenu, lpCreateParam);

調用的是派生類的Create函數,雖然派生類並沒有改寫這個函數,但是你可以這麼作並獲得靈活性。

下面繼續跟蹤這個Create的行爲,不用尋找了,這個函數就在CreateEx的上面一點。派生類沒有改寫,調用的就是基類中的版本。

       HWND Create(HWND hWndParent = NULL, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,      DWORD dwStyle = 0, DWORD dwExStyle = 0,               HMENU hMenu = NULL, LPVOID lpCreateParam = NULL)

       {

              ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);

              dwStyle = T::GetWndStyle(dwStyle);

              dwExStyle = T::GetWndExStyle(dwExStyle);

              if(rect.m_lpRect == NULL)

                     rect.m_lpRect = &TBase::rcDefault;

              return CFrameWindowImplBase< TBase, TWinTraits >::Create(hWndParent, rect.m_lpRect, szWindowName, dwStyle, dwExStyle, hMenu, atom, lpCreateParam);

       }

紅色標記了兩個重要的過程,一個註冊窗口類,一個創建了窗口。先關注窗口類的註冊。

窗口類與註冊

T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);

這條代碼完成了窗口類的註冊。

T是傳遞給基類的參數,也就是派生類。所以T就是CMainFrameT::GetWndClassInfo()表示,這裏調用的是類的靜態函數。那麼,這個函數在哪裏定義的呢?我們要注意到CMainFrame定義中的這麼一行:

DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)

顯然,這是一個宏(你看看後面沒有分號就知道了)。所以繼續搜索這個宏的定義

#define DECLARE_FRAME_WND_CLASS(WndClassName, uCommonResourceID) /

static CFrameWndClassInfo& GetWndClassInfo() /

{ /

       static CFrameWndClassInfo wc = /

       { /

              { sizeof(WNDCLASSEX), 0, StartWindowProc, /

                0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL }, /

              NULL, NULL, IDC_ARROW, TRUE, 0, _T(""), uCommonResourceID /

       }; /

       return wc; /

}

^_^,我逮着你了。就是這個宏把一個靜態函數搞進來了。這個靜態函數就是根據參數產生一個類型CFrameWndClassInfo的靜態變量,並返回它。這個靜態變量包含了WNDCLASS信息,以及和創建窗口時需要提供的一些信息。是一個所以

Register(&m_pfnSuperWindowProc);

調用的就是CFrameWndClassInfo中的member function。所以,我們要看看CFrameWndClassInfo的定義了:

class CFrameWndClassInfo

{

public:

       WNDCLASSEX m_wc;

       LPCTSTR m_lpszOrigName;

       WNDPROC pWndProc;

       LPCTSTR m_lpszCursorID;

       BOOL m_bSystemCursor;

       ATOM m_atom;

       TCHAR m_szAutoName[5 + sizeof(void*) * 2];  // sizeof(void*) * 2 is the number of digits %p outputs

       UINT m_uCommonResourceID;

       ATOM Register(WNDPROC* pProc)

       {

              if (m_atom == 0)

              {

                     ::EnterCriticalSection(&_Module.m_csWindowCreate);

                     if(m_atom == 0)

                     {

                            HINSTANCE hInst = _Module.GetModuleInstance();

                            if (m_lpszOrigName != NULL)

                            {

                                   ATLASSERT(pProc != NULL);

                                   LPCTSTR lpsz = m_wc.lpszClassName;

                                   WNDPROC proc = m_wc.lpfnWndProc;

                                   WNDCLASSEX wc;

                                   wc.cbSize = sizeof(WNDCLASSEX);

                                   // try process local class first

                                   if(!::GetClassInfoEx(_Module.GetModuleInstance(), m_lpszOrigName, &wc))

                                   {

                                          // try global class

                                          if(!::GetClassInfoEx(NULL, m_lpszOrigName, &wc))

                                          {

                                                 ::LeaveCriticalSection(&_Module.m_csWindowCreate);

                                                 return 0;

                                          }

                                   }

                                   memcpy(&m_wc, &wc, sizeof(WNDCLASSEX));

                                   pWndProc = m_wc.lpfnWndProc;

                                   m_wc.lpszClassName = lpsz;

                                   m_wc.lpfnWndProc = proc;

                            }

                            else

                            {

                                   m_wc.hCursor = ::LoadCursor(m_bSystemCursor ? NULL : hInst, m_lpszCursorID);

                            }

                            m_wc.hInstance = hInst;

                            m_wc.style &= ~CS_GLOBALCLASS;       // we don't register global classes

                            if (m_wc.lpszClassName == NULL)

                            {

                                   wsprintf(m_szAutoName, _T("ATL:%p"), &m_wc);

                                   m_wc.lpszClassName = m_szAutoName;

                            }

                            WNDCLASSEX wcTemp;

                            memcpy(&wcTemp, &m_wc, sizeof(WNDCLASSEX));

                            m_atom = (ATOM)::GetClassInfoEx(m_wc.hInstance, m_wc.lpszClassName, &wcTemp);

                            if (m_atom == 0)

                            {

                                   if(m_uCommonResourceID != 0) // use it if not zero

                                   {

                                          m_wc.hIcon = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(m_uCommonResourceID), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR);

                                          m_wc.hIconSm = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(m_uCommonResourceID), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);

                                   }

                                   m_atom = ::RegisterClassEx(&m_wc);

                            }

                     }

                     ::LeaveCriticalSection(&_Module.m_csWindowCreate);

              }

              if (m_lpszOrigName != NULL)

              {

                     ATLASSERT(pProc != NULL);

                     ATLASSERT(pWndProc != NULL);

                     *pProc = pWndProc;

              }

              return m_atom;

       }

};

不要管亂七八糟的一大堆,關鍵部分就是m_atom = ::RegisterClassEx(&m_wc);顯而易見,這一句完成了真正的窗口類的註冊。並用m_atom標記是否應註冊過了。關於窗口類的註冊,我們還要留意很關鍵的一點,那就是wndproc的地址。一路過來,明顯的就是StartWindowProc。好的,到此,窗口類的註冊已經完成了。下面:

窗口的創建

CFrameWindowImplBase< TBase, TWinTraits >::Create(hWndParent, rect.m_lpRect, szWindowName, dwStyle, dwExStyle, hMenu, atom, lpCreateParam);

這是這個函數的定義:

       HWND Create(HWND hWndParent, _U_RECT rect, LPCTSTR szWindowName, DWORD dwStyle, DWORD dwExStyle, _U_MENUorID MenuOrID, ATOM atom, LPVOID lpCreateParam)

       {

              ATLASSERT(m_hWnd == NULL);

              if(atom == 0)

                     return NULL;

              _Module.AddCreateWndData(&m_thunk.cd, this);

              if(MenuOrID.m_hMenu == NULL && (dwStyle & WS_CHILD))

                     MenuOrID.m_hMenu = (HMENU)(UINT_PTR)this;

              if(rect.m_lpRect == NULL)

                     rect.m_lpRect = &TBase::rcDefault;

              HWND    hWnd=::CreateWindowEx(dwExStyle, (LPCTSTR)(LONG_PTR)MAKELONG(atom, 0), szWindowName,              dwStyle, rect.m_lpRect->left, rect.m_lpRect->top, rect.m_lpRect->right - rect.m_lpRect->left, rect.m_lpRect->bottom-rect.m_lpRect->top, hWndParent, MenuOrID.m_hMenu,        _Module.GetModuleInstance(), lpCreateParam);

              ATLASSERT(m_hWnd == hWnd);

              return hWnd;

       }

if(atom == 0)

                     return NULL;

檢查窗口類是否已經正確註冊了。然後是CreateWindowEx實質的創建工作。裏面的參數窗口類是(LPCTSTR)(LONG_PTR)MAKELONG(atom, 0)。所以這裏,窗口類名沒有被使用。註冊窗口類時返回的atom被用作相應的功能了。這個和mfc的做法很不一樣。

到現在爲止,窗口類已經註冊並創建了一個窗口。我們回到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;

}

消息循環

AddMessageLoopRemoveMessageLooptheLoop掛到模塊(程序)對象上或者取下。

現在問題的核心是消息循環的處理。theLoop.Run();我們來看CMessageLoopRun的定義:

int Run()

       {

              BOOL bDoIdle = TRUE;

              int nIdleCount = 0;

              BOOL bRet;

              for(;;)

              {

                     while(!::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE) && bDoIdle)

                     {

                            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;

       }

很簡單,就是用PeekMessage決定當前是否有消息需要處理,然後在把需要處理的消息進行常規的翻譯和分發。其中有進行空閒時間處理的機會。

然後消息循環已經開始了,現在要關注是哪裏處理消息?

消息的處理

前面都是小菜,很清晰。到這裏才遇到了大問題。我們回憶到WNDCLASS中的wndproc記錄的是StartWndProc。不論如何,消息一開始進入的就是這個函數。抓住它,就有希望:

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);

}

首先我們來看SetWindowLong,知道這個是幹什麼的嗎?SetWindowLong改變窗口的一些基本屬性。GWL_WNDPROC表示要改變的是wndproc的地址。^_^,知道了爲什麼要先看這個了吧。這一步就是要把wndproc改爲“正確”的地方。也就是pProc

WNDPROC pProc = (WNDPROC)&(pThis->m_thunk.thunk);

這個就是執行了一次StartWindowProc之後,wndproc將改爲的函數。pThis是從_Module中取出來的。取出來的信息是窗口創建的時候記錄的,爲了清晰,先不管它,反正它是一個CWindowImplBaseT類型的指針。

現在要明白m_thunk是啥子東西。m_thunk定義在CWindowImplBaseT的基類中是類型爲CWndProcThunk的變量。我們來看CWndProcThunk

class CWndProcThunk

{

public:

       union

       {

              _AtlCreateWndData cd;

              _WndProcThunk thunk;

       };

       void Init(WNDPROC proc, void* pThis)

       {

#if defined (_M_IX86)

              thunk.m_mov = 0x042444C7;  file://C7 44 24 0C

              thunk.m_this = (DWORD)pThis;

              thunk.m_jmp = 0xe9;

              thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));

#elif defined (_M_ALPHA)

              thunk.ldah_at = (0x279f0000 | HIWORD(proc)) + (LOWORD(proc)>>15);

              thunk.ldah_a0 = (0x261f0000 | HIWORD(pThis)) + (LOWORD(pThis)>>15);

              thunk.lda_at = 0x239c0000 | LOWORD(proc);

              thunk.lda_a0 = 0x22100000 | LOWORD(pThis);

              thunk.jmp = 0x6bfc0000;

#endif

              // write block from data cache and

              file://  flush from instruction cache

              FlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk));

       }

};

恐怖吧,居然出現了機器碼。基本的思想是通過Init準備好一段機器碼,然後把機器碼的地址作爲函數地址。這段機器碼就幹兩件事情,一個是把wndprochwnd參數替換爲pThis,另外一個是跳轉到相應窗口的真實的wndproc中。

pThis->GetWindowProc()

這條代碼返回的就是實際處理消息的地方。現在來看這個函數:

virtual WNDPROC GetWindowProc()

       {

              return WindowProc;

       }

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中。這裏同樣使用了模擬虛函數。還有一個問題是我在CMainFrame中並沒有寫ProcessWindowMessage啊?但是你寫了

BEGIN_MSG_MAP(CMainFrame)

              MESSAGE_HANDLER(WM_CREATE, OnCreate)

              COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)

              COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)

              COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)

              COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)

              COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)

              COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)

              CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)

              CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)

       END_MSG_MAP()

這些東西其實就是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) /

              { /

              case 0:

#define MESSAGE_RANGE_HANDLER(msgFirst, msgLast, func) /

       if(uMsg >= msgFirst && uMsg <= msgLast) /

       { /

              bHandled = TRUE; /

              lResult = func(uMsg, wParam, lParam, bHandled); /

              if(bHandled) /

                     return TRUE; /

       }

#define COMMAND_ID_HANDLER(id, func) /

       if(uMsg == WM_COMMAND && id == LOWORD(wParam)) /

       { /

              bHandled = TRUE; /

              lResult = func(HIWORD(wParam), LOWORD(wParam), (HWND)lParam, bHandled); /

              if(bHandled) /

                     return TRUE; /

       }

#define CHAIN_MSG_MAP(theChainClass) /

       { /

              if(theChainClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult)) /

                     return TRUE; /

       }

#define END_MSG_MAP() /

                     break; /

              default: /

                     ATLTRACE2(atlTraceWindowing, 0, _T("Invalid message map ID (%i)/n"), dwMsgMapID); /

                     ATLASSERT(FALSE); /

                     break; /

              } /

              return FALSE; /

       }

至此,一切都明白了。其實WTL只是ATL的窗口部分的擴展,這裏所分析的東西絕大部分是ATL中的。而且這部分內容在《ATL INTERNAL》中也有比較詳細描述了。

歡迎訪問我的個人主頁http://www.noasia.net/taowen

發佈了61 篇原創文章 · 獲贊 1 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章