从接口模式开始COM编程

COM编程简介

COM(Component Object Model)即组件对象模型,COM是微软公司为了计算机工业的软件生产更加符合人类的行为方式开发的一种新的软件开发技术。
在COM构架下,人们可以开发出各种各样的功能专一的组件,然后将它们按照需要组合起来,构成复杂的应用系统。由此带来的好处是多方面的:

  • 可以将系统中的组件用新的替换掉,以便随时进行系统的升级和定制;
  • 可以在多个应用系统中重复利用同一个组件;
  • 可以方便的将应用系统扩展到网络环境下;COM与语言,平台无关的特性使所有的程序员均可充分发挥自己的才智与专长编写组件模块;

COM是开发软件组件的一种方法。组件实际上是一些小的二进制可执行程序,它们可以给应用程序,操作系统以及其他组件提供服务。开发自定义的COM组件就如同开发动态的,面向对象的API。多个COM对象可以连接起来形成应用程序或组件系统。并且组件可以在运行时刻,在不被重新链接或编译应用程序的情况下被卸下或替换掉。
Microsoft的许多技术,如ActiveX, DirectX以及OLE等都是基于COM而建立起来的。并且Microsoft的开发人员也大量使用COM组件来定制他们的应用程序及操作系统。

什么是接口?

在进行COM开发的过程中,可以说,我一直都在和各种各样的接口打交道。对于COM来说,接口是一个包含一个函数指针数组的内存结构,每一个数组元素包含的是一个由组件所实现的函数的地址;所以,对于COM,接口就是这样的一个内存结构,其它东西都是一些COM并不关心的实现细节。

在C++中,可以使用抽象基类来实现COM接口。由于一个COM组件可以支持任意数目的接口,因此对于组件,可以使用抽象基类的多重继承来实现它。

接口提供了两个不同对象间的一种连接。对于客户来说,一个组件就是一个接口集。客户只能通过接口才能同COM组件打交道。而整体上来讲,客户对于一个组件可以说是知之甚少;甚至在某些时候,客户甚至不必知道一个组件所提供的所有接口,就像你进行Windows Shell开发时,对于一个它提供的组件,很多时候,你不可能知道所有的接口的。对于一个应用程序而言,接口是最重要的。组件本身只不过是接口的实现细节。

在实际开发时,你并不需要去理会组件的实现细节,你面对的是接口,面对接口工作。即使组件的开发者将组件的实现替换掉了,而接口不变,你的程序也不需要变动。接口,就像一个标准一样,让我们去遵从这个标准。之前做的一个项目就是替换一个组件的实现层,而对于接口,则不需要进行变更。

COM底层接口IUnknown

COM的最底层的接口:IUnknown
简单来讲,IUnknown长这个样子:

struct IUnknown
{
    virtual HRESULT STDMETHODCALLTYPE QueryInterface( 
        /* [in] */ REFIID riid,
        /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject) = 0;

    virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;

    virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
}

所有的COM对象,必须继承IUnknown接口,并且实现它的语义:

  • QueryInterFace,这个函数是查找我们的接口,根据查找的接口通过第二个OUT参数接受查询接口的实现类的对象。

GUID是一种数据结构,在Windwos系统中可以通过guidgen命令来打开GUID,也可以通过API coCreateguid(查一下MSDN)我都是用windows自带的。我们第一个查找接口的函数中定义了Guid,这是为了我们查询接口准备的,每一个接口都会有一个GUID,guid是保证不重复的。

  • AddRef(): 表示此对象引用计数加一,比如被外面持有时,需要调用AddRef()。返回之后的引用计数。

引用计数存在是因为,如果我们每次查询是否存在就new一个对象,非常浪费空间,因此需要引用计数。

  • Release(): 表示此对象引用计数减一,一旦引用计数为0,实现者必须要释放此对象。

QueryInterface函数

客户可以调用QueryInterface来判断组件是否支持某个特定的接口。

所有的COM接口都需要继承IUnknown接口;因此,如果某个客户拥有一个IUnknown接口的指针,它并不需要知道它所拥有的接口指针到底是指向什么类型的,而只需要知道此接口可以用来查询其它接口就行了。

我们可以知道每个接口的vtbl中的前三个函数都是QueryInterface,AddRef和Release。这就使得所有的COM接口都可以被当成IUnknown接口来处理。如果某个接口的vtbl中的前三个函数不是这三个,那么它将不是一个COM接口。
由于所有的接口都是从IUnknown继承而来的,因此所有的接口都支持QueryInterface。所以,组件的任何一个接口都可以被客户用来获取它所支持的其他接口。由于所有的接口指针同时也将是IUnknown指针,客户并不需要单独维护一个代表组件的指针,它所关心的将仅仅是接口的指针。

AddRef与Release

D3D是 COM组件,它在服务进程中运行,而不在当前的客户进程中。在DX组件运行过程中,要创建一系列接口对象,如CreateDevice()返回接口指针,这些接口及其占用内存什么时候释放,要通过“引用计数”的技术来解决。AddRef()给这个接口指针的计数加1,而Release()会将之减1。一旦减到0,表示没有客户使用了,相关的接口就释放了。由此可知,每次调用Rlease()后,并不一定会释放内存,而是当引用计数归0时释放内存。

这样,对接口指针的使用,就像维护堆栈的平衡一样,而且按照某种约定规则使用。

接口的设计原则

  • 函数顺序不能改变
    接口的设计都是用的指针,都是虚表去查,简单来说就是通过虚函数来调用的,如果一单你的接口的顺序改变了,那么对应的虚表就会改变。

  • 参数不能改变
    接口的定义中明确了参数不可改变。

  • 兼容性
    我们会新定义一个接口类,让以前的实现类继承新的接口类,而新的接口类继承以前的接口类以确保新旧版本的兼容性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值