ATL 窗口类源码浅析

图1

对于典型的Win32应用程序而言,一般包括WinMain,窗口类注册,创建显示窗口,消息循环,消息处理。

下面是ATL Internals上面对上图的说明:

ATLWin32窗口API的封装如上图,其中粗体子的类比较重要,而其它的则是作为一般的辅助类,当然这个图并没有包括所有的内容,比如窗口类注册,消息循环等,下面我会一一道来。

1WinMainATL窗口程序中没有太大的变化,并没有先MFC那样直接Linkexe中,而是暴露给程序使用。

2窗口类注册在ATL中使用了一个如下的结构:

struct _ATL_WNDCLASSINFO

{

WNDCLASSEX m_wc;

LPCSTR m_lpszOrigName;

WNDPROC pWndProc;

LPCSTR m_lpszCursorID;

BOOL m_bSystemCursor;

ATOM m_atom;

CHAR m_szAutoName[5+sizeof(void)CHAR_BIT];

ATOM Register(WNDPROC* p)

{

return AtlWinModuleRegisterWndClassInfoA(&_AtlWinModule, &_AtlBaseModule, this, p);

}

};

另外在通过typedef定义为:typedef _ATL_WNDCLASSINFOW CWndClassInfo;

此结构是通过下面的define引入的:

/////////////////////////////////////////////////////////////////////////////

// CWndClassInfo - Manages Windows class information

#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; /

}

#define DECLARE_WND_CLASS_EX(WndClassName, style, bkgnd) /

static ATL::CWndClassInfo& GetWndClassInfo() /

{ /

static ATL::CWndClassInfo wc = /

{ /

{ sizeof(WNDCLASSEX), style, StartWindowProc, /

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

NULL, NULL, IDC_ARROW, TRUE, 0, _T(“”) /

}; /

return wc; /

}

#define DECLARE_WND_SUPERCLASS(WndClassName, OrigWndClassName) /

static ATL::CWndClassInfo& GetWndClassInfo() /

{ /

static ATL::CWndClassInfo wc = /

{ /

{ sizeof(WNDCLASSEX), 0, StartWindowProc, /

  0, 0, NULL, NULL, NULL, NULL, NULL, WndClassName, NULL }, /

OrigWndClassName, NULL, NULL, TRUE, 0, _T(“”) /

}; /

return wc; /

}

CWindowImpl中有定义DECLARE_WND_CLASS(NULL),因此任何从CWindowImpl派生的类都包含一个GetWndClassInfo函数并返回一个static CWndClassInfo用于注册窗口类,这也比较符合所有的窗口类产生的窗口对象都是一个样式的,比如你用BUTTON作为窗口类创建的肯定就是一个按钮了。上面第三个define是用来SuperClass用的,至于SuperClassSubClass的区别可以在MSDN上面找到,主要就是前者对整个窗口类而言,而后者只对某个窗口而言。

整个窗口类的注册比较复杂,下面是我根据源码分析的流程

 

图2



3窗口创建及显示部分,这里主要由CWindow来实现,这个和MFCCWnd不同处在于,CWnd把不仅包括对HWND形式API的封装还包括内部的CWndHWND之间的映射,消息映射,OLE等等,而CWindow仅仅只包括一个HWND数据成员,只对API进行了很浅的封装基本上就是一些ShowWindowSetWindowText之类的API的一个包装,而且CWindow在析构时也不会销毁窗口,对于窗口的消息映射等方面没有做了任何处理。

4消息映射部分,这里从图1可以看到一个CMessageMap的类,它负责窗口的消息映射

class ATL_NO_VTABLE CMessageMap



public:

virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,

LRESULT& lResult, DWORD dwMsgMapID) = 0;

};

CWindowImplRoot直接从CWindowCMessageMap派生,因此基本上具体有了窗口和处理消息的功能,但是为了减少模板带来的代码膨胀,这个类以及下面的CWindowImplBaseT只是起辅助作用,其中前者实现了对消息反射以及消息转发,消息反射是为了将子窗口中的通知消息反射回去以便于实现复用的控件,消息转发主要是在实现组合窗口中传递通知消息用的,当一个子窗口的父窗口还有父窗口,而这时子窗口消息又是要在最外层窗口中处理时就可以通过转发消息,两者的实现也不复杂:

template <class TBase>

LRESULT CWindowImplRoot< TBase >::ForwardNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

{

LRESULT lResult = 0;

switch(uMsg)

{

case WM_COMMAND:

case WM_NOTIFY:

case WM_PARENTNOTIFY:

case WM_DRAWITEM:

case WM_MEASUREITEM:

case WM_COMPAREITEM:

case WM_DELETEITEM:

case WM_VKEYTOITEM:

case WM_CHARTOITEM:

case WM_HSCROLL:

case WM_VSCROLL:

case WM_CTLCOLORBTN:

case WM_CTLCOLORDLG:

case WM_CTLCOLOREDIT:

case WM_CTLCOLORLISTBOX:

case WM_CTLCOLORMSGBOX:

case WM_CTLCOLORSCROLLBAR:

case WM_CTLCOLORSTATIC:

lResult = GetParent().SendMessage(uMsg, wParam, lParam);

break;

default:

bHandled = FALSE;

break;

}

return lResult;

}



template <class TBase>

LRESULT CWindowImplRoot< TBase >::ReflectNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

{

HWND hWndChild = NULL;



switch(uMsg)

{

case WM_COMMAND:

if(lParam != NULL) // not from a menu

hWndChild = (HWND)lParam;

break;

case WM_NOTIFY:

hWndChild = ((LPNMHDR)lParam)->hwndFrom;

break;

case WM_PARENTNOTIFY:

switch(LOWORD(wParam))

{

case WM_CREATE:

case WM_DESTROY:

hWndChild = (HWND)lParam;

break;

default:

hWndChild = GetDlgItem(HIWORD(wParam));

break;

}

break;

case WM_DRAWITEM:

if(wParam) // not from a menu

hWndChild = ((LPDRAWITEMSTRUCT)lParam)->hwndItem;

break;

case WM_MEASUREITEM:

if(wParam) // not from a menu

hWndChild = GetDlgItem(((LPMEASUREITEMSTRUCT)lParam)->CtlID);

break;

case WM_COMPAREITEM:

if(wParam) // not from a menu

hWndChild =  ((LPCOMPAREITEMSTRUCT)lParam)->hwndItem;

break;

case WM_DELETEITEM:

if(wParam) // not from a menu  

hWndChild =  ((LPDELETEITEMSTRUCT)lParam)->hwndItem;

 

break;

case WM_VKEYTOITEM:

case WM_CHARTOITEM:

case WM_HSCROLL:

case WM_VSCROLL:

hWndChild = (HWND)lParam;

break;

case WM_CTLCOLORBTN:

case WM_CTLCOLORDLG:

case WM_CTLCOLOREDIT:

case WM_CTLCOLORLISTBOX:

case WM_CTLCOLORMSGBOX:

case WM_CTLCOLORSCROLLBAR:

case WM_CTLCOLORSTATIC:

hWndChild = (HWND)lParam;

break;

default:

break;

}



if(hWndChild == NULL)

{

bHandled = FALSE;

return 1;

}



ATLASSERT(::IsWindow(hWndChild));

return ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam);

}



template <class TBase>

BOOL CWindowImplRoot< TBase >::DefaultReflectionHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult)

{

switch(uMsg)

{

case OCM_COMMAND:

case OCM_NOTIFY:

case OCM_PARENTNOTIFY:

case OCM_DRAWITEM:

case OCM_MEASUREITEM:

case OCM_COMPAREITEM:

case OCM_DELETEITEM:

case OCM_VKEYTOITEM:

case OCM_CHARTOITEM:

case OCM_HSCROLL:

case OCM_VSCROLL:

case OCM_CTLCOLORBTN:

case OCM_CTLCOLORDLG:

case OCM_CTLCOLOREDIT:

case OCM_CTLCOLORLISTBOX:

case OCM_CTLCOLORMSGBOX:

case OCM_CTLCOLORSCROLLBAR:

case OCM_CTLCOLORSTATIC:

lResult = ::DefWindowProc(hWnd, uMsg - OCM__BASE, wParam, lParam);

return TRUE;

default:

break;

}

return FALSE;

}

上面的DefaultReflectionHandler是在子窗口中对不感兴趣的反射消息进行默认的处理。

CWindowImplBaseT你用Thunk(这个原理也不是很懂大致就是用一些汇编方式的技巧修改内存中的某些数据吧)实现了窗口消息和对应的CWindowImplBaseT对象成员函数之间的消息映射,还有就是窗口的subclass和创建等。最后的CWindowImpl仅仅提供了一个Create函数在调用基类Create之前先进行窗口类的注册也就是图2的一系列流程。

这里还有CWinTraitsCWinTraitsOR它们主要是进行窗口样式的辅助设置。

5程序结构

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,

                   LPSTR szCmdLine, int nCmdShow)

{

CMyWindow wndMain; //这里CMyWindow派生自CWindowImpl

MSG msg;

 

    // Create & show our main window

    if ( NULL == wndMain.Create ( NULL, CWindow::rcDefault, 

                                 T(“My First ATL Window”) ))

    {

        // Bad news, window creation failed

        return 1;

    }

 

    wndMain.ShowWindow(nCmdShow);

    wndMain.UpdateWindow();

 

    // Run the message loop

    while ( GetMessage(&msg, NULL, 0, 0) > 0 )

    {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

    }

 

    return msg.wParam;

}



差不多大部分的ATL Window部分分析完了,虽然没怎么写文字,但是看一大串模板代码还是够呛的,尤其是注册窗口那部分绕来绕去的,++