科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道ATL的GUI程序设计(三)

ATL的GUI程序设计(三)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本章的内容也可以算是本书的核心部分——如果你要进行ATL的GUI程序设计的话,就必须将ATL的窗口类设计理念了然于心。

作者:李马 来源:CSDN 2007年9月24日

关键字:

  • 评论
  • 分享微博
  • 分享邮件

在本页阅读全文(共2页)

窗口事件响应的封装

窗口事件响应的封装,也就是这个类如何对窗口消息进行分流。你应该还记得,CHelloATLWnd类是通过BEGIN_MSG_MAP、END_MSG_MAP和MESSAGE_HANDLER宏实现的。如果你参阅了atlwin.h中它们的定义,你就会发现其实它们会组成一个ProcessWindowMessage函数。是的,CMessageMap就是由这个函数组成的:

/////////////////////////////////////////////////////////////////////////////
// CMessageMap - abstract class that provides an interface for message maps

class ATL_NO_VTABLE CMessageMap
{
public:
    virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
        LRESULT& lResult, DWORD dwMsgMapID) = 0;
};

CWindowImplRoot派生自CMessageMap,所以CWindowImplRoot及至CWindowImpl都需要实现ProcessWindowMessage以完成窗口消息的分流。大家可以看到,这个函数的前四个参数是在SDK程序设计中窗口回调的原班人马,在此不多介绍。lResult用来接收各消息处理函数的返回值,然后返回给最初的WndProc作为返回值。dwMsgMapID是一个神秘参数,且待李马留到以后再进行讲解。

“等等!”也许你会突然打断我,“——ATL是如何将WndProc封装到类的成员函数中的?”的确,在编译器的处理下,C++类中非static成员函数的参数尾部会被加入一个隐藏的this指针,这就使得它实际与回调函数的规格不合,所以非static成员函数是不能作为Win32的回调函数的。

先看MFC是如何做的吧。它采用一张庞大的消息映射表避开了这个敏感的地方,对此感兴趣的朋友们可参见JJHou先生的《深入浅出MFC》。也正因此,CWnd不得不为大部分消息各实现一个消息处理函数。还好这些消息处理函数不是虚函数,否则CWnd会维护多么庞大的一张虚函数表!

而ATL的奇妙之处也正是在此。它采用了thunk机制,即是在执行真正的WndProc回调之前刷改了内存中的机器码,将HWND参数用本窗口类的this指针替换了,然后在执行真正的代码之前再将这个指针转换回来。这样,就将this指针的矛盾巧妙化解了。由于本书讲解的是关于如何使用ATL进行GUI程序设计方面的内容,所以李马不在此进行过多探讨了就,感兴趣的朋友们可以自己研究atlwin.h中CWindowImplBaseT的代码,或者参考Zeeshan Amjad先生的《ATL Under the Hook Part 5》一文。

在thunk机制的帮助下,ATL的窗口类就可以直接将不感兴趣的消息交由DefWindowProc进行处理,而不用像MFC一样实现那么多消息处理函数。对于我们感兴趣的消息,可以使用ATL中的BEGIN_MSG_MAP/END_MSG_MAP宏来在窗口类的成员函数ProcessWindowMessage中完成。此外对于消息的分流,除了MESSAGE_HANDLER宏,我们还可以使用其它的几个宏进行各种消息(命令消息、普通控件通知消息、公共控件通知消息)的分流,我将在后边专门的一章中对ATL的CMessageMap的使用方法来进行讲解。

组合

葫芦兄弟单打独斗都不是蛇精的对手,所以葫芦山神就会派仙鹤携带七色彩莲找到他们,最后七个葫芦娃合体成为威力无比的葫芦小金刚,消灭了妖精,人世间重获太平……

这自然是一个非常老套的故事,但想必如我一样的80s生人看到后仍然会感慨不已。在那个少儿的精神食粮异常匮乏的年代,这部有些程式化脸谱化的动画片告诉了我们一个简单的道理:只有团结起来,才能发挥最大的力量。

ATL的窗口类也是如此,单凭CWinTraits、CWindow、CMessageMap这哥仨单打独斗是不可能成就大气候的。我们需要做的,就是使用某种方法来将它们组合起来。感谢C++为我们带来的多重继承和模板——多重继承让我们能够将它们组合,模板让我们能够将它们灵活地组合(所谓“灵活地组合”,即是在CWindowImpl层通过填入模板参数来决定继承链的顶层CWindowImplRoot的多重继承情况)。那么,再回到上一章的窗口类CHelloATLWnd:

class CHelloATLWnd : public CWindowImpl< CHelloATLWnd, CWindow, CWinTraits< WS_OVERLAPPEDWINDOW > >
{
public:
    CHelloATLWnd()
    {
        CWndClassInfo& wci     = GetWndClassInfo();
        wci.m_bSystemCursor    = TRUE;
        wci.m_lpszCursorID     = IDC_ARROW;
        wci.m_wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
        wci.m_wc.hIcon         = LoadIcon( NULL, IDI_APPLICATION );
    }
public:
    DECLARE_WND_CLASS( _T("HelloATL") )
public:
    BEGIN_MSG_MAP( CHelloATLWnd )
        MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
        MESSAGE_HANDLER( WM_PAINT, OnPaint )
    END_MSG_MAP()
public:
    LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& hHandled )
    {
        ::PostQuitMessage( 0 );
        return 0;
    }
    LRESULT OnPaint( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& hHandled )
    {
        HDC hdc;
        PAINTSTRUCT ps;

        hdc = BeginPaint( &ps );
        DrawText( hdc, _T("Hello, ATL!"), -1, &ps.rcPaint, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
        EndPaint( &ps );
        return 0;
    }
};

不知道你现在再看到这个类是否会少几分生疏?在这里,CWindowImpl就担任了“七色彩莲”的角色——BEGIN_MSG_MAP/END_MSG_MAP是CMessageMap由继承带来的,BeginPaint/EndPaint是CWindow由模板和多重继承带来的,以及控制窗口样式的CWinTraits(在这里要提醒一点,在将CWinTraits作为CWindowImpl的模板参数时,一定要将CWinTraits的模板参数右尖括号与CWindowImpl的模板参数右尖括号用空格分隔开,否则凑在一起的两个右尖括号“>>”将会被编译器判断为右移操作符)是由模板带来的。

当然,我还要回答上一章遗留下来的问题:WNDCLASSEX窗口类是如何注册的?

如果你是前已经偷偷看过CWindowImpl::Create的代码,那么相信这个问题你已经知道答案了。不过我还是要把相关代码列出来:

// from CWindowImpl::Create
if (T::GetWndClassInfo().m_lpszOrigName == NULL)
    T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();
ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);

也就是说,窗口类的注册是在窗口创建前完成的。

下面,李马请你注意上面代码中GetWndClassInfo的部分。这个函数是由窗口类的编写者——也就是我们,ATL的GUI开发者——完成的,它的主要功能是用来获取窗口类的属性。在通常的情况下,GetWndClassInfo使用DECLARE_WND_CLASS/DECLARE_WND_CLASS_EX的形式来实现。参看DECLARE_WND_CLASS宏的定义:

#define DECLARE_WND_CLASS(WndClassName) \
static CWndClassInfo& GetWndClassInfo() \
{ \
    static 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; \
}

这里已经为要注册的窗口类设置好了绝大多数的常用属性,当然,如果你仍然觉得自己需要更改更多的属性的话,可以像CHelloATLWnd的构造函数里那么做。特别要指出的一点是,ATL对窗口类的光标(cursor)属性是进行特殊处理的,对CWndClassInfo::m_wc.hCursor直接赋值是不行的。

编译期的虚函数机制

ATL的效率远远高于MFC,其中一方面的原因就是它把很多的工作都通过模板来交给编译器了,比如我上文提到的编译期的虚函数机制。这个机制可以避免虚函数带来的一切开销而静态实现虚函数的特性。考虑以下代码:

template < typename T >
class Parent
{
public:
    void f()
    {
        cout << "f from Parent." << endl;
    }
    void g()
    {
        T* pT = (T*)this;
        pT->f();
    }
};

class Child1 : public Parent< Child1 >
{
public:
    void f()
    {
        cout << "f from Child1." << endl;
    }
};

class Child2 : public Parent< Child2 >
{
};

然后,这样进行调用:

Child1 c1;
Child2 c2;
c1.g(); // f from Child1.
c2.g(); // f from Parent.

所有的奥秘尽在Parent::g之中,它通过一个类型转换在编译期就决定了调用哪个函数,颇有些多态性的味道。ATL就是借助这样的机制来保证效率的,如果你深入到atlwin.h的源代码之中,肯定会发现更多诸如此类的例子。



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1664816

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章