一、COM组件的目标:
COM组件的一个主要优势是:便于升级。
要实现这个优势需要满足一下两个条件:
1、运行时从客户程序动态加载和卸载,采用DLL技术可以实现。
2、为了更好的突出DLL的优势,还需要信息隐藏,即封装性。
二、COM组件的信息隐藏采用IUnknown接口来实现:
1、IUnknown接口功能简介:
IUnknown意思是未知,即未知的接口。采用这个名字是为了简单起见,所有的COM接口都需要继承IUnknown,因此若某个客户拥有一个IUnknown接口的指针,也就不知道接口到底是什么类型的,只需要知道此接口可以用来查询其他接口。IUnknown接口有一个用来查询接口的函数QueryInterface。由于所有的COM组件接口都从IUnknown接口继承,因此所有的接口都有QueryInterface函数,通过QueryInterface可以查询到任何接口,因此也就不需要单独维护一个代表组件的指针。
2、QueryInterface功能简介:
客户可以通过IUnknown中包含的QueryInterface函数查询某个组件是否支持某个特定的接口。
若支持,QueryInterface将返回指向此接口的指针。
否则,将返回一个错误代码。
然后,客户可以接着查询其它接口或将组件卸载。
3、QueryInterface函数遵循的规则:
QueryInterface的实现需要遵循一些规则,以便于客户能够获取关于组件的足够多的知识并对之实施一些控制和其他有用的处理。QueryInterface的行为必须是稳定的。
<1>、QueryInterface返回IUnknown接口时,总是返回同一IUnknown指针。
每个组件实例只有一个IUnknown接口,因此当查询组件实例的IUnknown接口时,不论通过那个接口查询,得到的均将是同一指针值。
总结:遵循这一规则能确定两个接口是否指向同一组件。
<2>、客户可以获取曾经得到过的接口。
若对于某个给定的接口,QueryInterface曾经成功过,那么对于同一组件的后续QueryInterface将总是成功的。
总结:若不遵循这一规则客户将无法通过编程的方法来决定组件到底有一些什么样的功能。
<3>、 可以再次获取已经拥有的接口,即通过A接口查询A接口指针。(自反性)
<4>、 客户可以从任何接口返回到起始接口,即若通过A接口可以查询到B接口,那么可以通过B接口查询到A接口(对称性)。
<5>、若能够从某接口获取到某特定的接口,则从任意的接口都将能获取此接口(传递性)。
若可以从A接口查询到B接口,从B接口查询到C接口,那么可以从A接口查询到C接口。
总结:这条规则使得QueryInterface是可用的,即某接口的查询不是必须依赖于某个接口查询。
COM对象的接口原则:
1、 对于同一个对象的不同接口指针,查询得到的Iunknow接口必须完全相同。
2、 接口对称性:对一个接口查询其自身总应该成功。
3、 接口自反性:如果从一个接口指针查询到另一个接口指针,则从第二个接口指针再回到第一个接口指针必定成功。
4、 接口传递性:如果从第一个接口指针查询到第二个接口指针,从第二个接口指针可以查询到第三个接口指针,则从第一个接口指针一定可以查询到第三个接口指针。
5、 接口查询时间无关性:如果在某一个时刻可以查询到某一个接口指针,则以后任何时候查询同样的接口指针,一定可以查询成功。
总结:COM为QueryInterface制定上述规则的目的使为了使QueryInterface使用起来更为简单、更富有逻辑性、更一致及更具确定性。
QueryInterface定义了组件,因此是COM最为重要的部分。组件所支持的接口集就是QueryInterface能够返回接口指针的那些接口。
三、COM组件新旧版本的处理:
COM组件一旦发布出去,接口将永远保持不变。接口不变是指不能改变接口(IID),但是可以建立一个新的接口(同时再为接口指定一个新的IID)。
1、COM组件对新旧版本的处理机制:
当QueryInterface接受到对老IID查询时,返回老的接口。
接收到对新的IID的查询时,它将返回新的接口查询。
对QueryInterface而言,一个IID就是一个接口,所以同某个IID相应的接口绝不会发生变化。
对于老的接口仍然保持不变,因此已有客户的运行将不会受到任何影响。
对于新客户则可以自行决定使用老接口还是新接口。
这种处理多个版本的方法最有效的地方在于它是无缝的。客户不需要做任何附加的动作,因为接口的标志符同其版本是完全绑定在一块的。
2、建立一个新版本的时机:
为了使COM处理多个版本的机制能够起作用,当下列条件中的任何一个改变时,就应给新接口指定新的IID:
<1>、接口中函数的数目。
<2>、接口中函数的顺序。
<3>、某个函数的参数。
<4>、某个函数的参数顺序。
<5>、某个函数的参数类型。
<6>、函数可能的返回值。
<7>、函数返回值的类型。
<8>、函数参数的含义。
<9>、接口中函数的含义。
总结:只要是所做的修改为导致已有客户的正常运行,都应为接口指定新的IID。
struct IUknown
{
//this function is used to retrieve other interface
virtual HRESULT _stdcall QueryInterface(const IID &iid,(void**)ip) =0;
//this is used to increament interface reference count
virtual ULONG _stdcall AddRef()=0;
//this is used to decrement interface reference ocunt
virtual ULONHG _stdcall Release() =0;
};