WTL for MFC Programmers
本文章总结自 这篇文章
本章内容
- ATL 背景知识
- ATL 窗口类
- ATL 窗口实现
- ATL 对话框实现
ATL 背景知识
WTL 是构建于 ATL 之上的一系列附加类。要学习 WTL 首先得对 ATL 进行一些介绍。
ATL 和 WTL 的发展历史
Active Template Library(活动模板库), 是为了方便进行 COM 组件和 ActiveX 控件开发而诞生的。由于 ATL 是为了开发 COM 而存在的,所以只提供了非常简单的界面类。直接用 ATL 开发界面程序是比较繁琐的。所以才会在此之上封装 WTL 来方便开发界面程序。
ATL 风格的模版
class CMyWnd : public CWindowImpl<CMyWnd>
{
// do something ...
};
上面的代码初看可能觉得很奇怪,为啥 CMyWnd 继承了 CWindowImpl, CWindowImpl 又拿 CMyWnd 当模版?这么做不会报错吗?这么做有什么作用?
首先,这样做不会报错,因为 C++ 的语法解释说即使 CMyWnd 类只是被部分定义,类名 CMyWnd 已经被列入递归继承列表,是可以使用的。
下面的例子解释了这种写法如何工作:
template <class T>
class B1
{
public:
void SayHi()
{
T* pT = static_cast<T*>(this);
pT->PrintClassName();
}
void PrintClassName() { printf("This is B1\n"); }
};
class D1 : public B1<D1>
{
// 没有覆写任何函数
};
class D2 : public B1<D2>
{
public:
void PrintClassName() { printf("This is D2\n"); }
};
int main()
{
D1 d1;
D2 d2;
d1.SayHi(); // This is B1
d2.SayHi(); // This is D2
return 0;
}
上述代码实现了类似于“虚函数”的多态功能。
通过这种模版写法, D2 继承的 B1.SayHi 函数,实际上被解释成:
void B1<D2>::SayHi()
{
D2* pT = static_cast<D2*>(this);
pT->PrintClassName();
}
SayHi 调用的是 D2 的 PrintClassName 方法。
如果不使用这种模版写法,那么 B1 的 SayHi 函数在调用 PrintClassName 的时候,只能去调用 B1 自己的 PrintClassName 函数,无法做到调用 D2 覆写后的 PrintClassName 函数。
这样做的好处如下:
- 不需要使用指向对象的指针,可以直接使用对象来调用多态接口;
- 节省内存,因为不需要虚函数表;
- 因为没有虚函数表所以不会发生在运行时调用空指针指向的虚函数;
- 所有的函数在编译时确定(区别于 C++ 的虚函数机制,在运行时确定调用哪个函数)。有利于编译程序对代码的优化;
回到最初的代码:
class CMyWnd : public CWindowImpl<CMyWnd>
{
// do something ...
};
这种写法的作用也就可以理解了。 CMyWnd 中覆写的函数,将能够以类似多态的方式被 CWindowImpl 正确调用。并且节省了虚函数表带来的内存开销。
ATL 窗口类
CWindow:
封装了所有对 HWND 的操作,几乎所有以 HWND 为第一个参数的窗口 API 都经过了 CWindow 的封装。 CWindow 类有一个公有成员 m_hWnd 使你可以直接对窗口进行操作。
CWindowImpl:
继承自 CWindow, 使用它可以对窗口消息进行处理,从而使窗口具有不同通过的功能和表现。另外它还封装了 窗口类的注册,窗口的子类化 等功能。
CAxWindow:
继承自 CWindow, 用于实现含有 ActiveX 控件的窗口;
CDialogImpl:
继承自 CWindow, 用于实现普通的对话框;
CAxDialogImpl:
继承自 CWindow, 用于实现含有 ActiveX 控件的对话框;
ATL 窗口实现:
要实现一个 ATL 窗口,要按照如下的步骤:
-
在 stdafx.h 中添加 ATL 相关的头文件:
#include <atlbase.h> // 基本 ATL 类 extern CComModule _Module; // 全局 _Module #include <atlwin.h> // 窗口 ATL 类
-
在 main.cpp 中定义 CComModule _Module 并初始化它:
#include "stdafx.h" CComModule _Module; int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { _Module.Init(NULL, hInstance); // 初始化 _Module // 在这里进行 ATL 窗口的创建、消息泵的创建 ... _Module.Term(); // 结束 _Module return 0; }
一个 ATL 程序包含一个 CComModule 类型的全局变量 _Module, 这和 MFC 程序都有一个 CWinApp 类型的全局变量 theApp 有点儿类似,唯一不同的是在 ATL 中这个变量必须被命名为 _Module.
_Module 在 main.cpp 中定义并初始化,并通过 extern 关键字在 stdafx.h 文件中声明,其他 #include "stdafx.h" 的模块就可以使用 _Module 来进行一些操作。 -
在 MyWindow.h 中定义自己的窗口 CMyWindow:
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits> { public: DECLARE_WND_CLASS(_T("My Window Class")) // 指定窗口类名 // 消息映射表 BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_CLOSE, OnClose) // 在这里将消息映射到函数 MESSAGE_HANDLER(WM_DESTROY, OnDestroy) // END_MSG_MAP() LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { DestroyWindow(); return 0; } LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { PostQuitMessage(0); return 0; } };
注意第一行代码
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
模板参数中的第一个,这样写的原因之前已经解释过,是为了实现类似“多态”的效果;
模板参数中的第二个,目前不知道原因;
模板参数中的第三个,用于指定窗口类型,如WS_OVERLAPPEDWINDOW
,WS_EX_APPWINDOW
等,CFrameWinTraits
是 ATL 预先定义的特殊类型,你也可以自己定义:typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,WS_EX_APPWINDOW> CMyWindowTraits; class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
-
在 main.cpp 中使用 CMyWindow 类创建主窗口:
#include "stdafx.h" #include "MyWindow.h" CComModule _Module; int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { _Module.Init(NULL, hInstance); // 声明 CMyWindow 对象 CMyWindow wndMain; // 创建窗口 if (NULL == wndMain.Create(NULL, CWindow::rcDefault, _T("My First ATL Window"))) { return 1; } wndMain.ShowWindow(nCmdShow); wndMain.UpdateWindow(); // 消息泵 MSG msg; while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } _Module.Term(); return msg.wParam; }
ATL 对话框实现:
要实现一个 ATL 对话框,和生成 ATL 窗口的方式差不多,只有两点不同:
窗口的基类是 CDialogImpl 而不是 CWindowImpl;
-
你需要在对话框类中定义名称为 IDD 的公有成员用来保存对话框资源的 ID;
#include "resource.h" class CAboutDlg : public CDialogImpl<CAboutDlg> { public: enum { IDD = IDD_ABOUT }; BEGIN_MSG_MAP(CAboutDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitControl) MESSAGE_HANDLER(WM_CLOSE, OnClose) COMMAND_ID_HANDLER(IDOK, OnOkCancel) COMMAND_ID_HANDLER(IDCANCEL, OnOkCancel) END_MSG_MAP() LRESULT OnInitControl(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { CenterWindow(); return TRUE; } LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { EndDialog(0); return 0; } LRESULT OnOkCancel(WORD wNotifyCode, WORD wID, HWND hWndCtrl, BOOL& bHandled) { EndDialog(wID); return 0; } };