`
wangxiaohigh
  • 浏览: 1426449 次
文章分类
社区版块
存档分类
最新评论

(好贴转载)MFC程序员的WTL开发指南---ATL界面类(1)

 
阅读更多
  导读

  WTL 具有两面性,确实是这样的。它没有MFC的界面(GUI)类库那样功能强大,但是能够生成很小的可执行文件。如果你象我一样使用MFC进行界面编程,你会觉得MFC提供的界面控件封装使用起来非常舒服,更不用说MFC内置的消息处理机制。当然,如果你也象我一样不希望自己的程序仅仅因为使用了MFC的框架就增加几百K的大小的话,WTL就是你的选择。当然,我们还要克服一些障碍:

  1) ATL样式的模板类初看起来有点怪异
  2) 没有类向导的支持,所以要手工处理所有的消息映射。
  3) MSDN没有正式的文档支持,你需要到处去收集有关的文档,甚至是查看WTL的源代码。
  4) 买不到参考书籍
  5) 没有微软的官方支持
  6) ATL/WTL的窗口与MFC的窗口有很大的不同,你所了解的有关MFC的知识并不全部适用与WTL。
  
  从另一方面讲,WTL也有它自身的优势:

  1) 不需要学习或掌握复杂的文档/视图框架。
  2) 具有MFC的基本的界面特色,比如DDX/DDV和命令状态的自动更新功能(译者加:比如菜单的Check标记和Enable标记)。
  3) 增强了一些MFC的特性(比如更加易用的分隔窗口)。
  4) 可生成比静态链接的MFC程序更小的可执行文件(译者加:WTL的所有源代码都是静态链接到你的程序中的)。
  5) 你可以修正自己使用的WTL中的错误(BUG)而不会影响其他的应用程序(相比之下,如果你修正了有BUG的MFC/CRT动态库就可能会引起其它应用程序的崩溃。

  如果你仍然需要使用MFC,MFC的窗口和ATL/WTL的窗口可以“和平共处”。(例如我工作中的一个原型就使用了了MFC的CFrameWnd,并在其内包含了WTL的CSplitterWindow,在CSplitterWindow中又使用了MFC的CDialogs -- 我并不是为了炫耀什么,只是修改了MFC的代码使之能够使用WTL的分割窗口,它比MFC的分割窗口好的多)。

  在这一系列文章中,我将首先介绍ATL的窗口类,毕竟WTL是构建与ATL之上的一系列附加类,所以需要很好的了解ATL的窗口类。介绍完ATL之后我将介绍WTL的特性以并展示它是如何使界面编程变得轻而易举。

  简介

  WTL是个很酷的工具,在理解这一点之前需要首先介绍ATL。WTL是构建与ATL之上的一系列附加类,如果你是个严格使用MFC的程序员那么你可能没有机会接触到ATL的界面类,所以请容忍我在开始WTL之前先罗索一些别的东西,绕道来介绍一下ATL是很有必要地。

  在本文的第一部分,我将给出一点ATL的背景知识,包括一些编写ATL代码必须知道的基本知识,快速的解释一些令人不知所措的ATL模板类和基本的ATL窗口类。

  ATL 背景知识 ATL 和 WTL 的发展历史

  “活动模板库”(Active Template Library)是一个很古怪的名字,不是吗?那些年纪大的人可能还记得它最初被称为“网络组件模板库”,这可能是它更准确的称呼,因为ATL的目的就是使编写组件对象和ActiveX控件更容易一些(ATL是在微软开发新产品ActiveX-某某的过程中开发的,那些ActiveX-某某现在被称为某某.NET)。由于ATL是为了便于编写组件对象而存在的,所以只提供了简单的界面类,相当于MFC的窗口类(CWnd)和对话框类(CDialog)。幸运的是这些类非常的灵活,能够在其基础上构建象WTL这样的附加类。

  WTL现在已经是第二次修正了,最初的版本是3.1,现在的版本是7(WTL的版本号之所以这样选择是为了与ATL的版本匹配,所以不存在1和2这样的版本号)。WTL 3.1可以与VC 6和VC 7一起使用,但是在VC 7下需要定义几个预处理标号。WTL 7向下兼容WTL 3.1,并且不作任何修改就可以与VC 7一起使用,现在看来没有任何理由还使用3.1来进行新的开发工作。

  ATL-style 模板

  即使你能够毫不费力地阅读C++的模板类代码,仍然有两件事可能会使你有些头晕,以下面这个类的定义为例:

class CMyWnd : public CWindowImpl<CMyWnd>
{
 ...
};

  这样作是合法的,因为C++的语法解释说即使CMyWnd类只是被部分定义,类名CMyWnd已经被列入递归继承列表,是可以使用的。将类名作为模板类的参数是因为ATL要做另一件诡秘的事情,那就是编译期间的虚函数调用机制。

  如果你想要了解它是如何工作地,请看下面的例子:

template <class T>
 class B1
 {
  public:
   void SayHi()
   {
    T* pT = static_cast<T*>(this); // HUH?? 我将在下面解释
    pT->PrintClassName();
   }
  protected:
   void PrintClassName() { cout << "This is B1"; }
 };

 class D1 : public B1<D1>
 {
  // No overridden functions at all
 };

 class D2 : public B1<D2>
 {
  protected:
   void PrintClassName() { cout << "This is D2"; }
 };

 main()
 {
  D1 d1;
  D2 d2;

  d1.SayHi(); // prints "This is B1"
  d2.SayHi(); // prints "This is D2"
 }

  这句代码static_cast<T*>(this) 就是窍门所在。它根据函数调用时的特殊处理将指向B1类型的指针this指派为D1或D2类型的指针,因为模板代码是在编译其间生成的,所以只要编译器生成正确的继承列表,这样指派就是安全的。(如果你写成: class D3 : public B1<D2>就会有麻烦) 之所以安全是因为this对象只可能是指向D1或D2(在某些情况下)类型的对象,不会是其他的东西。注意这很像C++的多态性(polymorphism),只是SayHi()方法不是虚函数。

  要解释这是如何工作的,首先看对每个SayHi()函数的调用,在第一个函数调用,对象B1被指派为D1,所以代码被解释成:

void B1<D1>::SayHi()
{
 D1* pT = static_cast<D1*>(this);
 pT->PrintClassName();
}

  由于D1没有重载PrintClassName(),所以查看基类B1,B1有PrintClassName(),所以B1的PrintClassName()被调用。

  现在看第二个函数调用SayHi(),这一次对象被指派为D2类型,SayHi()被解释成:

void B1<D2>::SayHi()
{
D2* pT = static_cast<D2*>(this);

pT->PrintClassName();
}

  这一次,D2含有PrintClassName()方法,所以D2的PrintClassName()方法被调用。

  这种技术的有利之处在于:

  1) 不需要使用指向对象的指针。
  2) 节省内存,因为不需要虚函数表。
  3) 因为没有虚函数表所以不会发生在运行时调用空指针指向的虚函数。
  4) 所有的函数调用在编译时确定(译者加:区别于C++的虚函数机制使用的动态编连),有利于编译程序对代码的优化。

  节省虚函数表在这个例子中看起来无足轻重(每个虚函数只有4个字节),但是设想一下如果有15个基类,每个类含有20个方法,加起来就相当可观了。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics