ATL 窗口类源代码学习笔记
本文是自己学习源代码的总结,在写作过程和察看代码寻找资料的时候,找到了些文章。
可参考:
1,CWindow
ATL有一个专门为窗口设计的基础类,可以做全部的窗口操作,这就是CWindow。它实际上就是对HWND操作的一个包装类,对几乎所有以HWND句柄为第一个参数的窗口API的进行了封装,例如:SetWindowText() 和 DestroyWindow()。CWindow类有一个公有成员m_hWnd,使你可以直接对窗口的句柄操作,CWindow还有一个操作符HWND,可以将CWindow对象传递给以HWND为参数的函数。
CWindow是一个普通的C++类。创建一个CWindow对象占用很少的资源,因为只有一个数据成员。
2,CWindowImpl继承树
在ATL类中对窗口过程的实现是CWindowImpl。CWindowImpl 含有所有窗口实现代码,例如:窗口类的注册,窗口的子类化,消息映射以及基本的WindowProc()函数。
我们先追根溯源,看看其继承过程:
一般来说,我们需要定义一个自己的窗口,首先就从CWindowImpl继承而来。
class CMyWnd : public CWindowImpl;
template
class ATL_NO_VTABLE CWindowImpl : public CWindowImplBaseT;
template
class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot;
template
class ATL_NO_VTABLE CWindowImplRoot : public TBase, public CMessageMap;
class ATL_NO_VTABLE CMessageMap
{
public:
virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
LRESULT& lResult, DWORD dwMsgMapID) = 0;
};
2.1,CMessageMap
该类是一个纯虚类,主要目的是提供对windows消息的处理接口。凡是继承自该类的子类,均有处理windows消息的函数了。
2.2,CWindowImplRoot
template
class ATL_NO_VTABLE CWindowImplRoot : public TBase, public CMessageMap;
首先看看TBase,这家伙是什么呢?就是我们在定义自己的窗口类的时候,传入的CWindow
class CMyWnd : public CWindowImpl;
TBase说明了我们自己定义的窗口类的祖先类是什么了。所以我们是不是可以从CWindow继承一个类来,然后将此子类作为类型参数传过去呢?完全可以。有了这个TBase,我们在写程序的时候,可以由自己控制从什么基类继承。
CWindowImplRoot定义了这几个成员,都是公有的。
CWndProcThunk m_thunk;
const _ATL_MSG* m_pCurrentMsg;
DWORD m_dwState;
它的几个成员函数主要是针对m_pCurrentMsg处理的。如:
// Current message
const _ATL_MSG* GetCurrentMessage() const
// "handled" management for cracked handlers
BOOL IsMsgHandled() const
void SetMsgHandled(BOOL bHandled)
还有就是对消息的推进和反射的支持方面。
// Message forwarding and reflection support
LRESULT ForwardNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT ReflectNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
static BOOL DefaultReflectionHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult);
似乎对 m_dwState,m_thunk没有做什么处理。
关于CWndProcThunk,需要看:
ATL中的Thunk机制学习,
http://dev.csdn.net/develop/article/20/20532.shtm
2.3,CWindowImplBaseT
template
class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot;
看看CControlWinTraits这玩意。
/////////////////////////////////////////////////////////////////////////////
// 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 CWinTraitsCControlWinTraits;
typedef CWinTraitsCFrameWinTraits;
typedef CWinTraitsCMDIChildWinTraits;
typedef CWinTraits CNullTraits;
template
class CWinTraitsOR
{
public:
static DWORD GetWndStyle(DWORD dwStyle)
{
return dwStyle | t_dwStyle | TWinTraits::GetWndStyle(dwStyle);
}
static DWORD GetWndExStyle(DWORD dwExStyle)
{
return dwExStyle | t_dwExStyle | TWinTraits::GetWndExStyle(dwExStyle);
}
};
原来它是对窗口属性的包装而已。定义都是静态函数。所以以后在创建窗口需要填入窗口属性参数的地方,我们可以简单的写:
CControlWinTraits::GetWndStyle(0);由此就得到了一个类似子控件的属性了。
或者我们可以定义自己的:
typedef CWinTraits CMyWindowTraits;
CWindowImplBaseT主要是对窗口过程的包装。
它的书记成员就一个窗口过程函数指针:
WNDPROC m_pfnSuperWindowProc;
该函数指针在构造的时候指向系统默认的窗口过程,和Win32 Api中一样的。
CWindowImplBaseT() : m_pfnSuperWindowProc(::DefWindowProc)
{}
而窗口属性在这个时候得到了支持了。
GetWndStyle
GetWndExStyle
有两个静态函数,也就是窗口过程函数吧,应该属于比较诡秘的技术,先略过。
WindowProc
StartWindowProc
2.4,CWindowImpl
template
class ATL_NO_VTABLE CWindowImpl : public CWindowImplBaseT
类中出现了一个宏:
DECLARE_WND_CLASS(NULL)
代表了什么呢?---〉
#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;
}
也就是定义了一个静态成员函数而已。
typedef struct tagWNDCLASSEXW {
UINT cbSize;
/* Win 3.x */
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
/* Win 4.0 */
HICON hIconSm;
} WNDCLASSEXW, *PWNDCLASSEXW, NEAR *NPWNDCLASSEXW, FAR *LPWNDCLASSEXW;
窗口类的定义通过DECLARE_WND_CLASS宏或DECLARE_WND_CLASS_EX宏来实现。这辆个宏定义了一个 CWndClassInfo结构,这个结构封装了WNDCLASSEX结构。DECLARE_WND_CLASS宏让你指定窗口类的类名,其他参数使用默认设置,而DECLARE_WND_CLASS_EX宏还允许你指定窗口类的类型和窗口的背景颜色,你也可以用NULL作为类名,ATL会自动为你生成一个类名。
其实我们在定义自己的窗口类的时候,也定义了这么一个宏。
class CMyWnd : public CWindowImpl
{
...
public:
DECLARE_WND_CLASS(_T("My Window Class"))
...
};
除了这个宏外,就剩下一个Create函数了。
HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,
DWORD dwStyle = 0, DWORD dwExStyle = 0,
_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL)
2.5,消息处理函数
接下来是我们在自定义类中对消息处理的定义,主要是使用类似下面的宏:
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()
至于这些宏还有哪些,以及其具体关系,我准备再写篇笔记学习之。上面的宏基本可以肯定的是,它定义了一个函数:
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0)
{
}
也就是消息处理函数,这个函数是个虚函数,定义在CMessageMap中,由CWindowImplRoot继承。
谁调用这个函数的呢?想想就该知道是在WindowProc中了,注册了窗口类后,成功创建完窗口,操作系统就会调用我们的窗口过程了。窗口过程应该在CWindowImplBaseT类中处理的。
template
LRESULT CALLBACK CWindowImplBaseT::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWindowImplBaseT* pThis = (CWindowImplBaseT*)hWnd;
// set a ptr to this message and save the old value
_ATL_MSG msg(pThis-m_hWnd, uMsg, wParam, lParam);
const _ATL_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_PTR pfnWndProc = ::GetWindowLongPtr(pThis-m_hWnd, GWLP_WNDPROC);
lRes = pThis-DefWindowProc(uMsg, wParam, lParam);
if(pThis-m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLongPtr(pThis-m_hWnd, GWLP_WNDPROC) == pfnWndProc)
::SetWindowLongPtr(pThis-m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis-m_pfnSuperWindowProc);
// mark window as destryed
pThis-m_dwState |= WINSTATE_DESTROYED;
}
}
if((pThis-m_dwState & WINSTATE_DESTROYED) && pThis-m_pCurrentMsg == NULL)
{
// clear out window handle
HWND hWnd = pThis-m_hWnd;
pThis-m_hWnd = NULL;
pThis-m_dwState &= ~WINSTATE_DESTROYED;
// clean up after window is destroyed
pThis-OnFinalMessage(hWnd);
}
return lRes;
}
CWindowImplBaseT* pThis = (CWindowImplBaseT*)hWnd;
这句有讲究,为何从一个句柄就可以得到类实例指针呢?我一时还没有搞明白。先不追究了。
在此函数中,主要点是:
(1)保存了原消息
(2)调用ProcessWindowMessage,也就是我们比较关注的消息处理函数调用源头。
(3)如果在自定义的窗口类中没有处理消息,则有ATL处理了。
(4)如果需要销毁窗口,则进行收尾处理,调用虚拟函数:OnFinalMessage。
总结:
通过上面对原代码的解读,我们可以发现ATL对窗口的创建,消息循环等的包装实质了。无非就是用模板机制,最终还是对Win32 API的调用。