1. IDL
- IDL 用C的语法定义COM接口。可以对同一个IDL文件生成C/C++,Java,和Visual Basic代码。
- IDL 定义了函数的返回值为32-bit的HRESULT。 当调用函数返回时,Java 虚拟机提供了对返回结果HRESULT 的处理机制,如果HESULT指示一个不正常的结果,那么Java虚拟机将会抛出一个异常。但是C/C++没有专门的运行库来支持COM接口的访问操作,所以需要手工去检测HRESULT是否正常,如:
if(FAILD(hr)) throw hr;
- 每个接口定义包括四个部分:接口名字,基接口名字,接口体和接口属性。
- 每个接口定义必须包含两个属性:
- [object] 说明该接口是COM接口而不是DEC(Distributed Computing Environment)接口
- [uuid]接口的实质名字。为了避免接口冲突,接口名字用GUID表示。COM中定义了对GUID的引用,并且重载了!=和==操作符。
- [local] 属性是可选的,用以禁止为该接口产生网络代码。
- IUnknow interface
这个接口是COM中唯一一个不继承其他接口的接口。它有QueryInterface, AddRef 和Release三个函数。COM的所有其他接口都是从这个接口派生的。当一个接口继承另一个接口的时候,为了保证被继承的接口的定义是可见的,要么在IDL文件里定义这个基类接口,要不用import 关键字(import “iunknow.idl”).
COM禁止拒绝多重接口继承,原因是:
- 不同的编译器多重继承的接口的对象模型是不一样的。而COM的本意是要让接口独立于(不依赖)便器。
- 第二个理由源自COM和DEC RPC之间的紧密联系。限制COM接口不能多重继承,COM接口和DEC RPC之间的映射就能直接进行。
- AddRef/Release 有十个规则,需要好好消化
- AddRef/Release 都会返回一个32-bit的数值,表示还没有释放掉的Object的计数。但是这个多线程,远程访问,多处理器会让这个数值可能不准确,所以这个数值没有什么意义。唯一有意义的是Release返回0的时候,表示已经没有对象可以使用了。在现实中比较有意义的是,严格的执行“Release之后不再使用对象”,这样就可以避免花大量时间去处理COM对象的计数。没有完全释放掉的对象,让操作系统去处理。当然最好的方法还是使用智能指针。
- QueryInterface的实现:在COM中经常出现接口多重继承基类接口情况(interface D: public B, public C)。当一个COM对象要暴露它的多个接口(包括基类接口)时, 应该用static_cast<>()函数对this指针进行转换。static_cast在编译时进行转换,转换的机制是将this指针做一定的偏移以找到指向合适的基类对象的指针。但是要注意,如果被继承的基类B和C分别继承了一个共同的基类A( B: public A; C: public A),这个时候D暴露的接口A有二义性:它可能暴露从B继承过来的A接口,也可能暴露从C继承过来的A接口。为了避免二义性,QueryInterface函数在query Interace A的时候应该返回B或者C,而不要返回A。这个时候一般都返回C继承的第一个基类B,因为编译器处理最左边的基类效率更高。
- COM 通过ErrorInfo抛出和处理异常。所有要抛出的对象要实现ISupportErrorInfo接口并在ISupportErrorInfo::InterfaceSupportInfo中指明要抛出异常的接口ID. 当客户代码调用SetErrorInfo的时候,抛出异常,这个时候COM拥有了一个指向这个异常对象的引用。调用GetErrorInfo来或者这个异常。COM异常可以映射到C++代码。在C++代码里将有一个struct COMException,在C++代码里用try catch 语句处理异常。但是要注意,不能将纯C++异常传播到方法之外。