WTL流程分析

    技术2022-05-11  78

    一个窗口从创建到销毁,有这么几个主要过程。

    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》中也有比较详细描述了。 


    最新回复(0)