《com 技术内幕》
第1章 组件
1、com,即组件对象模型,是关于如何建立组件以及如何通过组件建构应用程序的一个规范。
2、组件的优点:应用程序可随时间的流逝而发展变化;定制应用程序;组件库;分布式组件。
3、对组件的需求:组件必须动态连接;必须隐藏其内部实现细节。
4、com组件是以win32动态链接库(dlls)或可执行文件(exes)的形式发布的可执行代码组成
的。遵循com规范编写的组件将能够满足对组件家够的所有需求。com组件是动态链接的,com使
用dll将组件动态链接起来。对于com组件的封装是很容易的。com组件按照一种标准的方式来宣
布他们的存在。com组件是一种给其他应用程序提供面向对象的api或服务的极好方法。
5、com并不是一种计算机语言。
6、将com同dll相提并论是不合适的。实际上com使用了dll来给组件提供动态链接的能力。
7、com并不是像win32api那样的函数集,它更主要的是一种编写能够按面向对象api形式提供服
务的组件的方法。
8、com并不是类似于mfc这样的c++类库。com给开发人员提供的是一种开发与语言无关的组件库
的方法,但com本身并没有提供任何实现。
9、com具有一个被称作是com库的api,它提供的是对所有客户及组件都非常有用的组件管理服务
。
////////////////////////////////////////////////////////////////////
第2章 接口
1、在com中接口就是一切。
(1)接口可以保护系统免首外界变化的影响。
(2)接口可以使客户用同样的方式来处理不同的组件。
2、(1)com接口在c++中是用纯抽象基类实现的。
(2)一个com组件可以提供多个接口。
(3)一个c++类可以使用多继承来实现一个可以提供多个接口的组件。
3、类并非组件。
4、接口并非总是继承的。对接口的继承只不过是一种实现细节而已。除了可以使用一个类来实
现几个不同的接口外,还可以用单个的类来实现每一个接口再使用指向这些类的指针。
5、组件可以支持任意数目的接口。为支持多重接口,可以使用多重继承。支持多重接口的组件
可以被看作是接口的集合。
6、com接口的不变性、多态以及接口继承。
(1)一旦公布了一个接口,那么它将永远保持不变。当对组件进行升级时,一般不会修改已有
的接口,而是加入一些新的接口。
(2)多态指的是可以按同一种方式来处理不同的对象。
7、虚拟函数表(vtbl):包含一组指向虚拟函数实现的指针。
定义一个纯抽象基类也就是定义了相应的内存结构。但此内存只是在派生类中实现此抽象基类时
才会被分配。当派生类继承一个抽象基类时,它将继承此内存结构。
8、在com中,对一个组件的访问只能通过函数完成,而绝不能直接通过变量。
9、接口的真正的威力在于继承此接口的所有类均可以被客户按同一方式进行处理。
////////////////////////////////////////////////////////////
第3章 queryinterface函数
1、接口查询:
客户同组件的交互都是通过一个接口完成的。在客户查询组件的其他接口时,也是通过接口完成
的。这个接口就是iunknown。
iunknown接口的定义包含在win32 sdk中的unknown.h头文件中。
interface iunknown
{
virtual hresult _stdcall queryinterface(const iid& iid,void **ppv) = 0;
virtual ulong _stdcall addref() = 0;
virtual ulong _stdcall release() = 0;
}
在iunknown中定义了一个名为queryinterface的函数。客户可以调用queryinterface来决定组件
是否支持某个特定的接口。
2、所有的com接口都需要继承iunknown。
3、由于所有的com接口都继承了iunknown,每个接口的vtbl中的前三个函数都是
queryinterface,addref和release。若某个接口的vtbl中的前三个函数不是这三个,那么它将不
是一个com接口。由于所有的接口都是从iunknown 继承的,因此所有的接口都支持
queryinterface.因此组件的任何一个接口都可以被客户用来获取它所支持的其他接口。
4、非虚拟继承:注意iunknown并不是虚拟基类,所以com接口并不能按虚拟方式继承iunknown,
这是由于会导致与com不兼容的vtbl。若com接口按虚拟方式继承iunknown,那么com接口的vtbl
中的头三个函数指向的将不是iunknown的三个成员函数。
5、一个quertyinterface可以用一个简单的if-then-else语句实现,但case语句是无法用的,因
为接口标识符是一个结构而不是一个数。
6、多重类型及类型转换
7、queryinterface的规则
(1)queryinterface返回的总是同一iunknown指针。
(2)若客户曾经获取过某个接口,那么它将总能获取此接口。
(3)客户可以再次获取已经拥有的接口。
(4)客户可以从任何接口返回到起始接口。
(5)若能够从某个借口获取某特定接口,那么可以从任意接口都将可以获取此接口。
8、接口的iid决定了它的版本。当改变了下列条件中的任何一个时,就应给新接口指定新的id:
(1)接口中函数的数目。
(2)接口中函数的是顺序。
(3)某个函数的参数。
(4)某个函数参数的顺序。
(5)某个函数参数的类型。
(6)函数可能的返回值。
(7)函数参数的含义。
(8)接口中函数的含义。
9、避免违反隐含和约:
(1)使接口不论在其成员函数怎么被调用都能正常工作。
(2)强制客户按一定的方式来使用此接口并在文档中将这一点说明清楚。
//////////////////////////////////////////////////////////////////
第4章 引用计数
1、生命期控制
iunknown的另外两个成员函数addref和release的作用就是给客户提供一种让它指示何时处理完
一个接口的手段。
2、addref和release实现的是一种名为引用计数的内存管理技术。
引用计数是使组件能够自己将自己删除的最简单同时也是效率最高的方法。
com组件将维护一个称做是引用计数的数值。当客户从组件取得一个接口时,此引用计数值将增1
。当客户使用完某个接口后,组件的引用计数值将减1。当引用计数值为0时,组件即可将自己从
内存中删除。
3、正确使用引用计数规则:
(1)在返回之前调用addref。对于那些返回接口指针的函数,在返回之前应用相应的指针调用
addref。这些函数包括queryinterface及createinstance。这样当客户从这种函数得到一个接口
后,它将无需调用addref。
(2)在使用完接口之后调用release。在使用完某个接口之后应调用此接口的release函数。
(3)在赋值之后调用addref。在将一个接口指针赋给另外一个接口指针时,应调用addref。换
句话说,在建立接口的另外一个引用之后应增加相应组件的引用计数。
4、在客户看来,引用计数是处于接口级上而不是组件级上的。
5、为什么选择为每一个接口单独维护一个引用计数而不是针对整个组件维护引用计数?(1)使
程序调试更为方便;(2)支持资源的按需获取。
6、addref&release的例子
ulong _stdcall addref()
{
return interlockedincrement(&m_cref);
}
ulong _stdcall release()
{
if(interlockeddecrement(&m_cref)
{
delete this;
return 0;
}
return m_cref;
}
7、当建立一个新组件时,应建立一个对此组件的引用。因此创建组件时,在将指针返回给客户
之前,应该增大组件的引用计数值。这使程序员可以不必在调用createinstance 或
queryinterface之后记着去调用addref。
8、引用计数规则优化:
(1)输出参数规则:任何在输出参数中或作诶返回值返回一个新的接口指针的函数必须对此接
口指针调用addref。
(2)输入参数规则:对传入函数的接口指针,无需调用addref和release,这是因为函数的生命
期嵌套在调用者的生命周期内。
(3)输入-输出函数规则:对于用输入-输出参数传递进来的接口指针,必须在给它赋另外一个
接口指针之前调用其release。在函数返回之前,还必须对输出参数中所保存的接口指针调用
addref。如:
void exchangeforcachedptr( int i, ix **ppix)
{
(*ppix)->fx(); //do something with in-parameter.
(*ppix)->release();//release in parameter.
*ppix = g_cache[i];//get cached pointer.
(*ppix)->addref();//addref pointer.
(*ppix)->fx();//do something with out-parameter.