ATL揭秘之“对象创建”篇
程 化
1 问题
当我们用VC++ ATL工程创建了一个COM工程,实现了一个自己的COM对象,又在另一个程序中CoCreateInstance这个COM对象时,不知你是否想过这样的问题:COM对象是用C++类对象实现的,但是,我们从来没有在自己的代码中创建这些C++类对象——比如,“new”这些对象。那么,实现COM对象的C++对象是由谁,何时,以及如何创建的呢?
当然,简单而且正确的回答是:ATL在幕后帮助我们完成了这些工作。如果你不想了解ATL的工作细节,这样的回答应该是足够了。然而,ATL本身的思想就是“白盒”操作,想要用好ATL,就应该尽量多地了解ATL的工作细节。所以,搞清楚这个问题还是很有必要的。
想到这个问题后,出于懒惰的天性,我首先上网,试图找到别人对于这个问题的讲述,然而,大家要么讨论C++对象,要么讨论ATL其他的机制,似乎没有人特别关注ATL COM对象的创建过程,更比较少有人留心ATL如何将COM对象创建过程转换到C++对象创建过程上。
在研究这个问题的过程中,我逐渐发现这个问题很有意思,对这个问题的完整回答涉及了ATL相当多的基础结构。弄清楚了这个问题,对ATL的了解也会加深不少。
下面,我们就一起开始ATL对象创建揭秘之旅。
2 “对象”探讨
既然谈“对象创建”,则有必要对“对象”这个概念作一点讨论。在实际工作中,我感觉不少人对“对象”这个概念有不少误解;对“COM对象”也没有清晰的认识。
2.1 对象性质
这似乎是老生常谈了。对象性质,不就是“封装、继承、多态”这三个陈词滥调吗?然而,孔老夫子教导我们说:“温故而知新”。真理往往就蕴含在陈词滥调中。经过这些年的软件生涯,我对这句“陈词滥调”似乎有了更多地理解:
首先,“对象性质”是个独立的概念,也就是说,凡是具备了这个性质的东西就可以被称作对象。因此,一来“对象”不一定要用面向对象的语言编写,二来“对象”也可以具备各种环境下的语义——面向对象语言生成的对象是“编程语言”语义下的对象,如“C++对象”; 面向组件的开发生成的对象是“组件环境”语义下的对象,如“COM对象”。
其次,对象性质中的“继承”、“多态”需要好好斟酌。
什么是“继承”?是不是一定要用“CMyObject::CBaseObject”这样的语法才叫继承?当然不是,“继承”应该是对“对象层次结构”的有效处理。只要能够有效地处理对象层次结构,使低层对象能够自动具备高层对象的特性、行为,就应该可以被叫做“继承”。“CMyObject::CBaseObject”干的是什么事?不就是把CBaseObject的成员变量复制给了CMyObject,并且使CMyObject的对象能够调用CBaseObject的公有和保护方法吗?
再说“多态”。C++语言说“多态”就是支持虚函数调用,这样讲对,但是局限在C++语言本身上。“虚函数调用”是某些语言的特性,难道没有虚函数的语言就无法支持多态了?其实“多态”这个词本身译得很好,直抒其意——“多姿多态”。“多态”本质上是“运行时决定行为”。只要能够在运行时才决定如何行动,而不是在编译时决定,就是“多态”。
综合看来,“继承”和“多态”都不是面向对象语言的专利,其他的语言,只要能够通过某种机制实现这些特性,就可以实现“对象”。
2.2 COM对象
COM规范对于COM对象如何做到“封装、继承、多态”有自己的规定。该规定不依赖具体语言,不依赖具体的操作系统环境,所以,我们说COM规范是语言中立和平台中立的(当然,提供平台的人并不中立,这和规范的中立是两码事)。
- “封装”:COM对象只处理行为封装,其工具是“接口”;
- “继承”:COM的继承不是源代码级别,是二进制代码级别。COM对象提供了两种方式来继承对象的二进制代码——“包容”和“聚合”;
- “多态”:COM的“运行时决定行为”能力来自不同对象实现同一接口。使用COM的统一方式——QueryInterface,我们可以找到不同COM对象对同一接口的实现,从而实现“运行时决定行为”。
当然,COM对象除了这老三样之外,还要其他性质,其中最重要的就是对象的“生命周期管理”。“生命周期管理”通过AddRef和Release这两个“引用计数”函数实现。
3 ATL COM对象
ATL实现COM对象的基本思路是:针对不同的COM对象性质,分层处理。不同的