| 潘爱民 提供 2000/06/12 未经作者允许任何机构或个人不得以任何方式转载或复制本文 |
|
atl之深入浅出 介绍一本关于atl的书《atl internals》 潘爱民,5月7日,2000年 北京大学计算机研究所,100871 引言 面对计算机图书市场的繁荣景象,我经常感叹今天学习计算机开发技术的同道们是多么幸运。十年前,我们学习计算机语言非常不容易,要掌握各种开发工具只有靠自己的摸索和极少量的参考手册。我记得,94年我学习visual c++和mfc的时候,基本上只有靠软件自带的联机帮助;现在情形大不同了,书店中的计算机图书琳琅满目,关于visual c++和mfc的书籍尤其多。有几位编辑朋友劝我写一点这方面的书,我觉得不大有必要了,因为visual c++的好书已经不少了,适合各种读者层次的书籍几乎都可以买到,而且有一些书还相当不错。不过,在98年的时候,我感觉关于com书籍实在太少,几乎没有,于是我下定决心,自己写一本关于com的书籍,在99年底的时候由清华出版社出版。很快地,关于com的书籍已经很多了,有些国外的名著也相继引入国内。从这十多年的计算机开发技术历史来看,一门技术只要有很多的书籍来介绍,那么这项技术很快就会普及,否则就难以推广。 说到com,相信在windows平台上有过开发经验的朋友一定接触过,它是windows操作系统的基本软件模型,从93年建立以来,为windows平台的推广和发展做出了不可磨灭的贡献,而且其自身还在不断发展。但是要真正开发com组件并不是很轻松,在visual c++中,我们既可以使用mfc也可以使用atl。mfc完全面向windows应用,它用c++的封装技术建立了一套适合于开发windows应用的c++类库,虽然在后期的版本中mfc提供了大量的com支持,但是从基本的设计结构上讲,mfc不适合于开发专业的com组件,它适合于在windows应用的基础上提供相应的com支持。 atl则不同于mfc,它完全面向com组件,其技术路线也不同于mfc,mfc使用的是c++中的继承、封装、嵌套等常规技术,而atl使用了c++中模板、多继承等高级技术,甚至还用到了stl。所以学习和使用atl要求我们必须熟悉这些c++高级特性。另一方面,atl结构完全针对com中的诸多规范,这就要求使用人员必须非常了解com规范,才有可能真正把atl用好。 虽然目前关于mfc的书籍很多,但是完全介绍atl的书籍非常少,甚至根本没有,这不能不说是一个遗憾。我有幸在今年2月份看到一本atl的英文原版书《atl internals》,本文将为大家介绍这本书。 在看这本书之前,我对atl已经有了基本的了解,98年底由于写作的需要,我曾经读过atl的部分源码,对于atl的基本结构还算清楚。我刚开始看到这本书的时候,快速读了一章,并没有感觉这本书有多好,后来由于工作忙碌的原因,一直没有得闲,直到最近,我才仔细把这本书读了一遍,感觉这是一本不可多得的好书。以前我很少仔细阅读开发技术类的书籍,一则是由于自己读书太慢,二则是往往开发技术类的书籍不大值得精读。但是这本书我读得很仔细,因为这本书把atl的精华几乎全表述出来了,atl中的许多内容都能让你为之心动,作为一个程序员,这也是一个学习和提高的机会。 我写这篇文章的意图不仅仅是向读者介绍这本书,我也希望能够把我在阅读过程中的心得与大家分享。同时我还希望能够按照这本书的路线,向大家介绍一下atl的结构和机理。 准备阅读 《atl internals》由addison-wesley出版社出版,作者为brent rector和chris sells,出版时间为1999年2月,全书600多页。关于这本书的背景知识可能对于许多com迷和atl迷来说很有意思。首先,这本书的序言是由atl的发明人jim springfield所撰写,在序言中,jim介绍了atl的历史,对于书中所介绍的内容大加赞赏,称赞“阅读此书可以学到许多阅读源码所不能掌握的内容”,在序言的最后,jim还谈到了atl的将来。 其次,在作者写的自序中,他们提到了atl离不开com,要想掌握atl,就一定要先掌握com。atl是一个产生c++/com代码的框架,就如同c语言是一个产生汇编代码的框架,这个观点颇为新颖,仔细想来,确是如此。作者特别推荐了don box的书《essential com》,其实在英文书中,com书籍不少,这显示出两位作者与don box的关系非同一般。我记得don box也曾经推荐过《atl internals》这本书,如果读者有机会到亚马逊网上书店(www.amazon.com)看看本书的书评就可以知道他们之间有很亲密的关系,其中作者之一chris sells与don box以及另外两人合作写了一本com的书《effective com》(本书文中有多处推荐了这本书)。我相信他们的互荐是基于相互之间绝对了解的基础之上的,他们都是com顶尖高手,也是atl顶尖高手,读者经常可以在msj(microsoft system journal)杂志上看到他们的文章。 诚如作者所言,阅读此书需要极强的预备知识,按照我阅读此书之后的理解,读者在阅读此书之前应该有以下几方面的准备知识:
所以说,《atl internals》是一本起点很高的书,原因在于atl是一门起点很高的技术。据我所知,现在有许多程序员已经在使用atl了,这是好现象,说明我们国内的程序员水平相当高,虽然我们的资料信息还不够丰富(至少对于atl而言是这样),但是我们仍然紧跟这些新的技术。尽管如此,要想真正用好atl,一定要了解atl的机理,这不同于mfc的情形。假如我们不懂mfc的机理,一样可以做出很好的程序,利用mfc,在不懂ole细节的情况下,一样可以提供ole的支持。atl要求我们很细致地调节它的类,根据需要选择合适的模板类,必要时还要修正它的行为。atl尽管已经到了3.0版本,但是仍然有不少的错误,这本书已经指出了一些错误,但我相信肯定还会有更多的错误,这对程序员提出了更高的要求,确实是这样,因此结论是:使用atl一定要懂atl! 尽管我这样说,但我还是认为atl是一项好技术、是一个好的com模板类库。而且我也深感好书对于atl程序员的重要性,有些东西是不能从源码和参考手册获得的,既然我看到了这本好书,那我应该把这本书介绍出来,让大家知道这本书。也让大家分享我的体会。如果有那家出版社能够引进这本书的话,则是我们广大atl程序员的福音了。 内容介绍 下面我按照《atl internals》的叙述顺序,逐项介绍atl的内容,希望读者不仅能够了解本书的内容,也能够借此了解atl的机理。原书共包括十一章,我按照每一章所介绍的内容把这十一章分成四个部分。 第一部分 atl的使用和功能展示 这一部分篇幅很小,只有短短的一章内容,读起来非常轻松。初时我以为整本书都是这样的,所以感觉这也是一本指南性质的书,当时就没有太重视。 在这一章里,作者简单介绍了atl的概念,然后通过appwizard和atl object wizard创建com服务程序和com对象类的过程逐项介绍了wizard中各个选项的含义。有了基本的工程框架和对象类框架之后,作者开始展示atl的一些其他特性:
作者介绍这些内容非常简捷,但是能够让读者有一些基本的印象,在阅读后续内容之前对atl有一个清晰的思路。如果读者对com比较熟悉而且曾经有过atl开发经验的话,读这部分内容会非常轻松。 第二部分 atl实现com:基础部分 这部分介绍了atl实现com的基本方法,包括四章内容,分别如下:atl smart types、objects in atl、com servers、interface maps。这四章内容是atl的精华所在,也是这本书的精华所在,如果说全书其他的内容都是可读可不读的话,那么这部分的内容则是不可不读的。 第二章介绍了atl的智能类型(smart type),包括字符串类型、variant类型以及接口指针类型。com规范要求所有的字符必须用双字节字符,不是我们常用的ansi字符,所以对于字符串的处理往往是我们编程工作中所必须面对的任务,虽然谈不上有多困难,但是往往要花掉我们不少时间来处理这些琐事。作者在这一章首先介绍了与字符表达有关的许多概念以及多种转换途径,然后介绍了atl的基本字符类型封装类ccombstr以及ccomvariant。作者对于这两个类的介绍非常细致,指出了每个成员函数的一些细节,甚至个别缺陷。我在阅读时,对这一部分很感兴趣,虽然这些内容我基本上都知道,每个成员函数也可以通过atl的源码得到其细节知识,但是读下来仍然感觉受益匪浅。反过来,这一章最后部分介绍的智能指针类,我的兴趣并不大,大概是我对智能指针一直存有偏见的原因吧,不过智能指针在atl中用得很广泛,书中后面部分到处可见智能指针的应用。atl的智能指针封装类是一个比较典型的、功能全面的智能指针模板类,有兴趣的读者可以读一读这两个类(ccomptr和ccomqiptr)的源码。 第三章的开篇就是com的套间(apartment)和com的线程模型,虽然篇幅很短,但是这些内容很重要。因为atl是一个支持多线程模型的com类库,为了支持多线程,代价是非常昂贵的,要求com对象的每一个细节都要涉及到并发的可能性,atl既要考虑到代码的大小,又要考虑到代码的运行效率,所以atl用了许多技巧来保证其方案的有效性。介绍了线程模型之后,作者又讲述了实现com基本接口iunknown的一些考虑要点。之后再给出atl对象的层次图,如果读者对于atl中类的结构还不了解的话,那么这个层次图可以让你知道wizard生成的类与其他的类有什么样的关系,这个图可以指导你阅读完后续的两章。有了这些准备知识之后,作者详述了atl为对象提供的线程模型支持,限于本文的篇幅,我不能详细讲述这些内容。 讲述了线程模型之后,作者进一步介绍com对象的基类ccomobjectrootex实现iunknown相关的方法,有了线程模型的基础后实现引用计数非常简单,但是queryinterface成员函数不是基类就能够完成的,它需要用到ccomobjectrootex派生类也就是wizard生成的类所提供的接口信息。atl通过接口映射表的形式提供对象的接口信息,通过多继承的方式实现多接口的支持。 com对象的实例化非常与众不同,因为wizard生成的类还只是一个抽象类,所以它不能够直接被实例化,即我们不能用new操作符生成一个对象。真正的对象类应该是ccomobject,它实现了iunknown的所有方法,并提供了一个用于创建对象的函数。如果对象支持聚合模型的话,那么最终的对象类应该是ccomaggobject。为了统一两种情况以便减小最终的代码量,atl提供了ccompolyobject类作为最终的对象类。 atl的创建过程并不复杂,但是它提供了多阶段构造(multiphase construction),允许我们在创建过程中加入更多的控制代码,获得更大的灵活性。作者对这一部分的介绍甚为细致,还解释了atl_no_vtbl宏即novtbl编译指示符的含义,如果一个类声明了novtbl指示符,那么编译器在派生类的构造过程中不为基类产生虚表(vtable)。 这一章的内容是atl的基础,读起来并不难,但是一定要清楚atl类的层次关系,否则很容易陷入atl的复杂语法之中。 第四章介绍com服务程序的atl支持,作为一个com服务程序,它的主要任务是管理对象的注册、为每个对象提供一个类厂、以及自身的生存期管理。对于进程内组件和进程外组件还需要区别对待。atl实现的对象分为可通过类厂创建的对象和不可创建的对象,不可创建的对象不需要类厂的支持,往往为服务程序中其他的对象所用。atl实现这些功能主要通过对象映射表和ccommodule类。 对象映射表是一个全局表,其中包括当前服务程序所实现的所有对象类,表中的每一项包括对象的clsid、注册该对象信息的函数、创建类厂的函数、创建对象的函数等等。有了这些信息,服务程序就可以管理它所支持的每一个对象类。回过头来,为了让对象映射表管理好这些工作,每一个对象类也需要提供相应的函数或者信息,比如对象的注册函数、创建函数等。atl的注册功能很强,除了标准对象的注册支持之外,它可以提取出内嵌在资源中的注册脚本文件(registry script file),实现更为灵活、功能强大的注册操作。对于使用者来说,只要编写资源脚本再加上一个宏声明即可。atl对于类厂的支持在ccomclassfactory类中实现,对象类从ccomcoclass继承一个类厂创建类的定义_classfactorycreatorclass。ccomclassfactory类的实现没有用到模板参数,而是内嵌一个创建函数,由该函数完成实际的创建工作。这个过程并不复杂,书中讲得很清楚,而且书中还介绍了atl实现iclassfactory2的方法。 ccommodule是com服务程序的主线,当我们创建一个atl工程的时候,visual c++都会为我们生成一个ccommodule派生类,并且定义一个全局变量_module,这就如同我们在mfc工程中使用cwinapp应用类一样。ccommodule类的许多成员函数都对应了它所应该完成的任务,比如更新注册表的操作、获取类厂对象、注册类厂对象(对于进程外服务程序)等。 第四章介绍的内容对于我们理解atl工程的总体思路非常有用,结合com规范对com实现中的所有细节要求,atl给出了一种高效、针对小尺寸组件的实现方案。结合第三章的内容,就构成了atl实现com的基本技术框架。 第五章讲述atl的接口映射表,实际上这是对第三章内容的补充,但是因为atl的接口映射表比较灵活,而且多接口支持对于com对象非常重要。所以作者单独用一章的篇幅来讲述接口映射表。对于多接口的对象,com有很严格的规范来指示客户程序调用这些接口成员函数,特别是iunknown::queryinterface。为了遵循这些规范,并保持一定的灵活性,atl使用了接口映射表的技术。接口映射表的原理非常简单,它以表的形式记录了每个接口的iid以及接口的vtable与对象类的this指针之间的偏移,但是atl的接口映射表并没有这么简单,它以函数的形式把这样的逻辑封装起来,从而允许用户使用更为灵活的接口查询策略。 atl用多继承的方法实现多接口的支持,如果两个接口的方法名和参数重合的话,这时就会产生问题,书中介绍了一种避免名字冲突的方法,方法并不复杂,但很有效。这一章还介绍了一种被称为“接口着色”的技术,其实很简单,只不过是按照com所要求的虚表结构另行构造而已,其好处是可以实现两个接口语义完全相同但是iid却不相同的接口。这也体现了com接口实现的灵活性。 除了支持多继承方式的接口之外,atl有一个很强大的接口支持就是对于动态接口的支持,书中称为“tear-off interface”。每一个动态接口类都应该从ccomtearoffobjectbase派生,以后当客户向对象请求该接口的时候,对象类会调用接口映射表中指定的创建函数创建该接口对象。 除了动态接口技术应用了接口映射表这种结构之外,还有对象类对聚合接口的支持,实现形式与动态接口非常类似,atl对聚合的支持分有计划聚合(planned aggregation)和盲聚合(blind aggregation),可以说,atl对聚合的支持比较全面,但是我们在使用的时候一定要谨慎,com中的有些特性往往隐含着潜在的出错可能性,比如说盲聚合就是这样的一种特性。 这一章最后介绍了接口映射表的一些诀窍,包括接口映射表的链结构、拒绝支持某个接口、利用接口请求进行调试、对接口映射表的扩展(比如,利用接口映射表设置后门,通过后门得到对象类的this指针;以及基于对象实例的接口请求)。这一章所讲述的内容非常细节,涉及到com规范中的许多细微的地方,读懂这一部分并不难,但是要求读者具有有关的com背景知识。 以上四章内容是atl的基石,即使把这一部分独立出来也可以构成一本书“atl深入浅出”,如果读者要依靠atl来编写com组件的话,那么认真读懂这一部分就可以奠定工作的基础。如果有人说atl使用c++语法非常花哨的话,那么他们一定是指这一部分所讲述的内容。由于c++模板语法本身的复杂性,加上atl在许多模板类的定义中使用了“typedef”,再加上atl也使用了类似mfc的宏结构,所以读起源码来非常晦涩。尽管作者讲述这一部分内容非常有条理,但我看这几章的时候不免要前后翻动,偶尔还要查看一下atl的源码。但是一旦明白了atl的思路,又不免为它的设计所折服。 第三部分 atl实现com:扩展部分 atl实现com的扩展部分包括三章内容,分别为:persistence in atl(atl的永久特性支持)、collections and enumerators(集合对象和枚举器对象)、connection points(连接点对象)。这三章是面向com应用层面的三个大方向,也是我们比较常用的一些com特征。如果读者要全面掌握atl的话,那么应该读一读这部分。 第六章介绍atl对com永久机制的支持,相对来说,这一章内容的介绍读起来要轻松得多,只要读者对com永久模型比较熟悉即可。由于com永久模型的复杂性主要位于客户程序一方,在对象一方只需要实现有关的几个永久接口,当然这些永久接口与对象本身的逻辑是密切相关的。这一章前面部分回顾了ipersistpropertybag、ipersiststream[init]、ipersiststorage永久接口的定义和实现,然后介绍atl对这些永久接口的实现,重点介绍了属性映射表(property map)。atl提供的永久接口的实现能够自动对属性映射表中的属性进行永久处理,即提供load和save支持。对于属性映射表不支持的永久内容(比如说书中所举的索引属性的例子),我们可以在适当的地方进行重载处理,atl允许我们在多个地方重载这套机制。 在介绍了这几个常用的永久接口之后,作者还介绍了ipersistmemory接口,并细致说明了几个永久接口公共的成员函数getsizemax的重要性以及作者补充的实现方法。在这一章的最后,作者还给出了一个用永久特性实现自定义列集(marshaling)的一种方法,如果读者对自定义列集有兴趣的话,可以看一看这一章最后几页的介绍。 第七章介绍了com集合对象和枚举器对象(enumerator)的atl实现。在讲述集合和枚举器对象之前,作者先介绍了stl中的容器和迭代器(iterator),这是stl中数据组织和数据访问的基本形式,然后作者以一个类比,指出虽然stl不能直接用于com,但是com提供了类似的对象组织和访问机制,这就是com集合对象和枚举器对象。 com的集合对象是构成com对象模型的基础,为了在客户程序一方特别是vb(visual basic)或者vba作为客户程序时,它能够方便有效地访问集合对象,com制定了集合对象的接口规范以及枚举对象的接口规范。atl实现了这些规范,并且在atl内部,还提供了多种途径来管理这些成员数据或者成员对象。 atl的集合对象实现起来比较简单,只要按照com规范,增加集合对象所特有的属性:count、item、_newenum即可。_newenum属性把集合对象和枚举对象联系起来了。在atl中,枚举数组类为ccomenum,它以数组的形式管理其成员数据,值得一提的是,atl在实现枚举接口的时候,为了方便对于数据的拷贝操作,专门抽象出一个被称为“拷贝策略”的类,由该类的静态成员函数实施成员拷贝操作。atl真正实现枚举接口的类为ccomenumimpl,它是ccomenum的基类,ccomenumimpl的实现并不复杂,唯一值得注意的是ccomenumimpl内部保存数据的方式,既可以是快照方式,也可以引用集合对象中的数据。有了这些基础,加上上一部分介绍的atl对象类,实现枚举对象就非常容易了,作者在书中用一个素数集合对象的例子讲述了整个过程,最终通过素数集合对象的_newenum属性把它与素数枚举对象联系起来。
本文关键:VC,COM , ATL, DCOM
|