ATL & WTL 实现分析(一)

    典型的Win32程序可以分为这几部分:

  1. 注册窗口类(WNDCLASSEX的一个实例,可认为是一个类对象);
  2. 创建窗口对象(HWND hwnd来保持);
  3. 创建窗口过程(WndProc,在注册窗口类时完成WndProc的注册,本质是回调函数);
  4. 创建并开始消息循环(MSG对象)。

    整个过程有很多繁琐和例行的步骤,而窗口部分Win32 API设计意图还是以对象模型实现的(尽管不那么完美,扁平而繁杂)。程序员天性懒惰,又喜欢新奇的tricks;c++语言的面向对象实现,模板,这一切便造就了ATL/WTL的实现。

 ATL window class

  一、  CWindow

    主要作用:

  1. 创建及保持窗口对象(HWND hwnd)。实现了构造、赋值、创建、释放(Detach)。
  2. CWindow是和窗口相关的所有Win32 API的一个wrapper,实现了几乎所有以hwnd作为第一参数的API函数薄封装。
  3. CWindow的成员函数都是内联实现,所以无运行时代价;此外还完成了繁琐“卫述“功能。

     在使用ATL窗口类之前,需要将以下代码添加到stdafx.h中。所有的ATL窗口应用都由CComModule对象_Module来保存,类似MFC的CWinApp类。

#include <atlbase.h>
extern CComModule _Module;
#include <atlwin.h>

 

      则使用CWindow的一个改进Win32程序可以如下:

int APIENTRY _tWinMain(HINSTANCE hinst,
                       HINSTANCE /*hinstPrev*/
                       LPTSTR    pszCmdLine,
                       int       nCmdShow)
{
  //初始化ATL module
  _Module.Init(0, hinst);

  //注册窗口类
  ...

  //创建窗口对象
  CWindow wnd;
  wnd.Create (pszMainWndClass, 0, CWindow::rcDefault, _T("Hello Windows App"),
              WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDGE);
  if (!wnd) return -1;
 
  //show the main window
  ...
 
  //Shut down the ATL module
  _Module.Term();

  return msg.wParam;
}  

 

   对应的窗口过程,类似下面:

LRESULT CALLBACK WndProc (HWND hwnd, UINT nMsg, 
                          wPARAM wparam, LPARAM lparam)
{
  switch (nMsg) {
    //WM_PAINT handler
    case WM_PAINT: {
      PAINTSTRUCT ps;
      CWindow     wnd(hwnd);
      HDC         hdc = wnd.BeginPaint( &ps);
      Rect        rect;
      wnd.GetClientRect( &rect);
      DrawText(...);
      wnd.EndPaint( &ps);
      break;
      ...
  }
  return 0;
}

   显然,仅仅是对窗口对象创建和保持的简单封装,远远不够。但this is a step in the right direction.

2. CWindowImpl

  CWindowImpl继承自CWindow,主要功能:

  1. 窗口类注册(WNDCLASSEX);
  2. 消息处理。

  其实现类似下面:

template <class T, class TBase /* = CWindow */, class TWinTraits /* = CControlWinTraits */>
class ATL_NO_VTABLE CWindowImpl : public CWindowImplBaseT< TBase, TWinTraits >
{
public:
    DECLARE_WND_CLASS(NULL) 

    static LPCTSTR GetWndCaption()
    {
        return NULL;
    }

    HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,/*注意窗口类无名*/
            DWORD dwStyle = 0, DWORD dwExStyle = 0,
            _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL);
    {
        if (T::GetWndClassInfo().m_lpszOrigName == NULL)
            T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();//DECLARE_WND_CLASS定义了这个成员函数。
        ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);

        dwStyle = T::GetWndStyle(dwStyle);
        dwExStyle = T::GetWndExStyle(dwExStyle);

        // set caption
        if (szWindowName == NULL)
            szWindowName = T::GetWndCaption();

        return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect, szWindowName,
            dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);
    }
}

  窗口类的名称由宏DECLARE_WND_CLASS实现(待分析)。CWindowImpl维持了了一个static数据结构CwndClassInfo,其定义如下(Unicode模式):

struct _ATL_WNDCLASSINFOW
{
    WNDCLASSEXW m_wc;
    LPCWSTR m_lpszOrigName;
    WNDPROC pWndProc;
    LPCWSTR m_lpszCursorID; //注意:本来应该是hCursor属性,现在通过这两项来维持。
    BOOL m_bSystemCursor;   // 谁知道ATL team怎么想的……
    ATOM m_atom;
    WCHAR m_szAutoName[5+sizeof(void*)*CHAR_BIT];
    ATOM Register(WNDPROC* p)
    {
        return AtlWinModuleRegisterWndClassInfoW(&_AtlWinModule, &_AtlBaseModule, this, p);
    }
};

  m_wc代表了窗口类结构(同Win32程序中的wc)。而m_atom表示该窗口类是否已注册过。(很奇怪的是,这个结构体中竟然有一个函数?结构体是class,那么这个函数应该是一个成员函数咯。)接着看DECLARE_WND_CLASS的实现:

#define DECLARE_WND_CLASS(WndClassName) \
static ATL::CWndClassInfo& GetWndClassInfo() \
{ \
    static ATL::CWndClassInfo wc = \
    { \
        { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, \
          0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL }, \
        NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \
    }; \
    return wc; \
}

  这个宏很是具有迷惑性,展开后是一个成员函数。它其实是定义了一个类内“单件”,在这个函数内部保存,仅仅可通过这个函数访问。这时,若要修改窗口类的属性,就可以通过它来修改了:

class CMainWindow: public CWindowImpl <CMainWindow> {
    CMainWindow(){};
    ...
};
//.cpp
CMainWindow() {
    CwndClassInfo & wci = GetWndClassInfo();
    if (!wci.m_atom) {
        wci.m_wc.hIcon = LoadIcon (_Module. ...);
        wci.m_wc. ...;
    }
}

    2.1 Window Trait

    从上面的ATL窗口类图可以看到,有一个辅助类WindowTrait,它提供了对风格的一个封装。从而可以将常用的组合直接作为模板参数传入窗口类。此类定义如下:

// CWinTraits - Defines various default values for a window
template <DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0>
class CWinTraits
{
public:
    static DWORD GetWndStyle(DWORD dwStyle)
    {
        return dwStyle == 0 ? t_dwStyle : dwStyle;
    }
    static DWORD GetWndExStyle(DWORD dwExStyle)
    {
        return dwExStyle == 0 ? t_dwExStyle : dwExStyle;
    }
};

 

  使用方法:

typedef CWinTraits<WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDEG> CMainWinTraits;

class CMainWindow: public CWindowImpl <CMainWindow, CWindow, CMainWinTraits>{...};

 

  此时,窗口创建就可以如下:

wnd.Create(0, Cwindow::rcDefault, _T("Hello Atl"));//省却了风格参数。

  实际上,ATL为frame windows、child windows,以及MDI child window分别预定义了一下风格:

typedef CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0>  CControlWinTraits;
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>  CFrameWinTraits;
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_MDICHILD>  CMDIChildWinTraits;

  如果仅仅是想为现有窗口增加一些风格,可以用CWinTraitsOR类,使用方法如下:

typedef CWinTraitsOR<0, WS_EX_CLIENTEDGE, CFrameWinTraits> CMainWinTraits;

 

小结:

  1. 给出了ATL的类图;
  2. 分析了最基础、最直观的CWindow实现;
  3. 分析了CWindowImpl的窗口类注册实现,详细分析了DECLARE_WND_CLASS*成员函数*的作用;
  4. 分析了辅助类WindowTrait。
posted @ 2013-03-18 22:14  MacroLee  阅读(1502)  评论(0编辑  收藏  举报