WTL

WTL for MFC Programmers, Part I

2016-04-12  本文已影响182人  董哒哒

WTL for MFC Programmers

本文章总结自 这篇文章

本章内容

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 函数。

这样做的好处如下:

  1. 不需要使用指向对象的指针,可以直接使用对象来调用多态接口;
  2. 节省内存,因为不需要虚函数表;
  3. 因为没有虚函数表所以不会发生在运行时调用空指针指向的虚函数;
  4. 所有的函数在编译时确定(区别于 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 窗口,要按照如下的步骤:

  1. 在 stdafx.h 中添加 ATL 相关的头文件:

    #include <atlbase.h>       // 基本 ATL 类
    extern CComModule _Module; // 全局 _Module
    #include <atlwin.h>        // 窗口 ATL 类
    
  2. 在 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 来进行一些操作。

  3. 在 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>
    
  4. 在 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 窗口的方式差不多,只有两点不同:

  1. 窗口的基类是 CDialogImpl 而不是 CWindowImpl;

  2. 你需要在对话框类中定义名称为 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;
        }
    };
    
上一篇 下一篇

猜你喜欢

热点阅读