典型的Win32程序可以分为这几部分:
- 注册窗口类(WNDCLASSEX的一个实例,可认为是一个类对象);
- 创建窗口对象(HWND hwnd来保持);
- 创建窗口过程(WndProc,在注册窗口类时完成WndProc的注册,本质是回调函数);
- 创建并开始消息循环(MSG对象)。
整个过程有很多繁琐和例行的步骤,而窗口部分Win32 API设计意图还是以对象模型实现的(尽管不那么完美,扁平而繁杂)。程序员天性懒惰,又喜欢新奇的tricks;c++语言的面向对象实现,模板,这一切便造就了ATL/WTL的实现。
一、 CWindow
主要作用:
- 创建及保持窗口对象(HWND hwnd)。实现了构造、赋值、创建、释放(Detach)。
- CWindow是和窗口相关的所有Win32 API的一个wrapper,实现了几乎所有以hwnd作为第一参数的API函数薄封装。
- CWindow的成员函数都是内联实现,所以无运行时代价;此外还完成了繁琐“卫述“功能。
在使用ATL窗口类之前,需要将以下代码添加到stdafx.h中。所有的ATL窗口应用都由CComModule对象_Module来保存,类似MFC的CWinApp类。
#include
extern CComModule _Module;
#include
则使用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,主要功能:
- 窗口类注册(WNDCLASSEX);
- 消息处理。
其实现类似下面:
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(){};
...
};
//.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
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 CMainWinTraits;
class CMainWindow: public CWindowImpl {...};
此时,窗口创建就可以如下:
wnd.Create(0, Cwindow::rcDefault, _T("Hello Atl"));//省却了风格参数。
实际上,ATL为frame windows、child windows,以及MDI child window分别预定义了一下风格:
typedef CWinTraits CControlWinTraits;
typedef CWinTraits CFrameWinTraits;
typedef CWinTraits CMDIChildWinTraits;
如果仅仅是想为现有窗口增加一些风格,可以用CWinTraitsOR类,使用方法如下:
typedef CWinTraitsOR<0, WS_EX_CLIENTEDGE, CFrameWinTraits> CMainWinTraits;
小结:
- 给出了ATL的类图;
- 分析了最基础、最直观的CWindow实现;
- 分析了CWindowImpl的窗口类注册实现,详细分析了DECLARE_WND_CLASS*成员函数*的作用;
- 分析了辅助类WindowTrait。