3.COM接口
关于接口,想说的很多!
3.1.接口的定义
接口是包含了一组成员函数的数据结构,这组成员函数是组件对象暴露出来的信息,客户程序利用这些函数获得组件对象的服务。
(接口的调用机制)客户程序用一个指向接口数据结构的指针来调用接口成员函数,接口指针又指向另一个指针,这第二个指针指向一组函数,称为接口函数表(可以联想到C++中的虚函数表),接口函数表中每一项为4个字节长的函数指针,每个函数指针与对象的具体实现连接起来。通过这种方式,客户只要获得了接口指针,就可以调用到对象的实际功能。结构如图示:
对于一个接口,虚函数表是确定的,成员函数个数和顺序是不变的;对于每个成员函数来说,其参数和返回值类型是确定的。在接口定义中,所有这些信息都必须在二进制级别确定,这样才能实现二进制级别的代码复用,并且只要能支持这样的内存结构的描述的开发语言,就可以定义接口。
3.1.1C语言描述
说明:
(1) 每一个接口成员函数的第一个参数为指向IDictionary的指针,这就类似于C++类定义中隐藏的this指针,用来提供当前对象实例的属性信息。
(2) 在接口成员函数中,字符串变量必须用Unicode字符指针,COM规范要求使用Unicode字符,所以在组件程序内部用到了ANSI字符的话,则应该进行两种字符表达的转换。
(3) 接口成员函数的调用习惯也是确定的。在Windows平台上有两种调用习惯:一种是_cdecl调用支持函数可变参数调用(printf),一种是_stdcall,由于大多数的语言都使用了_stdcall调用习惯,COM规范也采用了_stdcal调用习惯。
3.1.2C++语言描述
我们不难发现,COM接口定义中的vtable与class的vtable(类的虚函数表)完全一致,因此用class描述COM接口是最为方便的:
在class中隐藏了vtable,每个成员函数隐藏了第一个参数this指针,this指针指向类的实例,下图为类IDictionary的内存结构:
3.1.3IDL接口定义语言描述
COM在IDL的基础上,进行扩展形成了COM接口描述语言。接口描述语言提供了一种不依赖于任何语言的借口描述方法,因此,它可以成为组件程序和客户程序之间共同的语言。VC++提供了MIDL可以把IDL接口描述文件编译成C/C++兼容的接口描述头文件。
3.2接口的内存模型
上面我们看到了接口的定义方式,但是看到的只是一些函数描述,并没有看到COM接口与COM对象的关系,我们知道每个成员函数的第一个参数是一个指向接口自身的this指针,可以为我们提供COM对象的信息,但是this指针如何与对象状态信息联系起来呢?
为此,我们还需要定义类来实现已定义的接口,并且类中有相关属性的描述信息。接口IDictionary和字典对象的内存结构如图示:
如果同一个CLSID的类定义了两个字典对象,则显然两个字典对象公用了成员函数,但数据属性不能公用,此时内存结构如图示:
如果两个不同CLSID的类定义了两个字典对象,则内存结构如下图:
3.3接口的一些特点
二进制特性
接口不变性
继承性(扩展性)
运行过程中的多态性