COMM 接口简介
知道了CLSID或ProgID唯一地表示一个组件服务程序,那么根据这些ID,就可加载运行组件,并为客户端程序提供服务。启动组件程序的方法,会陆续介绍。
接下来先讨论如何调用组件提供的函数?接口。
作为客户端程序员,希望或要求:写一个程序时,不做任何修改可调用任意一个组件。举例来说:
在Word中可嵌入Excel,也可嵌入Picture,或任何第3方发表的ActiveX文档。也就是说,连Word自己都不知道使用它的人将会在doc里面插入什么东东;可在HTML文件中插入一个ActiveX,也可插入一个程序脚本Script,自己写一个插件也可插入到IE环境中。为完成这个功能,绝对也不会去让微软修改IE吧!这个要求实在有点难度,Office开发停滞了。说来话巧,一天老O(Office项目总工程师)和小B(VB项目总工程师)一起喝酒,老O向小B倾诉了它的烦恼:
- 老O:怎么能让我写的程序C,可调用其它人写的程序S中的函数?(C表示客户程序,S表示提供服务的程序)
- 小B:你是不是喝糊涂了?把S作成DLL,你去LoadLibrary、GetProcAddress、…FreeLibrary!
- 老O:废话!要这么简单就好了。问题是,连我都不知道这个S程序是干什么的?能干什么?我怎么调用呀?
- 小B:VB中制定了一个标准,允许任一VB开发者,把它写的小程序放在VB工具栏上,这就好象它扩展了VB的功能。
- 老O:这个VBX标准是什么?
- 小B:特简单,就是在VBX中必须实现7个函数,功能是:初始化、释放、显示、消息处理……,而至于它内部干什么,我管不着。我只是在需要时调用这7个函数。
- 程序C要能调用任何人写的程序B。那么B必须要按照事先要求,提供需要的函数F1(),F2(),F3(),K1(),K2()。
- BASIC是解释执行,因此它的函数不用考虑书写顺序,只要给出函数名,解释器就能找到。
编译后的C++代码中没有函数名,只有函数地址,因此必须改进为用VTAB(虚函数表)表示的函数入口:
C++ VTAB的结构如下:
C++多个VTAB的结构:
- 还需要改进,因为所有函数地址放在一个表中不灵活、不易扩展。有了!按函数功能分类:
- 现在有2个VTAB表,怎么从一个表找到另一个表那?又有办法了,要求必须实现一个函数,且这个函数地址必须放在所有表的开头(表中的第一个函数指针),这个函数就叫QueryInterface,完成从一个表查找到另一个表的功能。
除了QueryInterface函数,顺便也完成另外两个函数,叫AddRef和 Release。
- 为了以后描述方便,不再使用图四方法,而使用下图这样简洁的样式:
接口(Interface)概念
函数通过VTAB虚函数表提供其地址。从另一个角度看,不管用什么语言开发,编译器产生的代码都能生成这个表。这样就实现了组件的“二进制特性”跨语言要求。假设有一个指针变量保存着VTAB的首址,这个变量就叫“接口指针”,变量命名时,习惯加上”I”开头。另外,为区分不同的接口,每个接口也都要有一个名字,该名字和CLSID一样,使用GUID方式,叫IID。
- 接口一经发表,就不能再修改。不然就会出现向前兼容的问题。这个性质叫“接口不变性”。
- 组件中必须有3个函数,QueryInterface、AddRef、Release,这3个函数也组成一个接口,叫”IUnknown”。
- 任何接口,其实都包含了IUnknown接口。随着接触到更多接口就会体会到接口的另一个性质“继承性”。
- 在任何接口上,调用表中的第一个函数QueryInterface,就会得到另一个接口指针。这个性质叫“接口的传递性”。
- 在C/C++中,函数需要事先声明,这就要求组件提供C语言头文件。为使COM具有跨语言的能力,因此不再为任何语言提供对应函数接口声明,而是独立提供一个称为类库(TLB)的声明。
- 每个语言IDE环境,根据TLB生成各自语言的包装。这个性质叫“接口声明的独立性”。
小结
这里介绍了两个非常重要的概念:CLSID和Interface。IDispatch接口IID是多少?
想知道为什么COM函数总返回 HRESULT吗?想知道如何使用BSTR、VARIANT吗?
想知道COM中该如何使用内存吗?想知道如何使用UNICODE吗?
- 注1:GUID全局唯一标示符,CLSID/IID其实是借用了GUID的概念。
- 注2:ProgID = Program ID,等价于CLSID,是用串表示的。
- 注3:注册表子键ProgID和VersionIndependentProgID分别表示真正的ProgID和版本无关的ProgID。我的计算机安装了Excel,它的ProgID=”Excel.Application.9”,VersionIndependentProgID=”Excel.Application”。
- 注4:Interface = 接口,以前微软不叫它接口叫协议Protocol。
- 注5:IUnknown的IID=IID_IUnknown;注册表样式{00000000-0000-0000-C000-000000000046}。
- 注6:TLB是由一个描述接口的文件IDL经过编译产生的。IDL说明见后续文章。
- 注7:IPersistStorage是用复合文件的存贮(Storage)功能来保存/读取数据用的一个接口。
- 注8:IPersistStreamInit 是用复合文件的流(Stream)功能来保存/读取数据用的一个接口。