COM组件程序:模块,它可以是 动态连接库(DLL) && 可执行程序(EXE),称为 进程内组件(in-of-process component) && 进程外组件(out-of-process component)。
COM对象:建立在二进制可执行代码级的基础上,因此COM对象是语言无关的,这一特性使得使用不同编程语言开发的组件对象进行交互成为可能。
COM标准:规范 && 实现。规范:定义了组件和组件之间的通信机制,不依赖于特定的语言和操作系统;实现:COM库,COM库为COM规范的具体实现提供了一些核心服务。实现包括一些核心的系统级代码,这部分核心代码使得对象和客户之间可通过接口在二进制代码级进行交互 。
标志对象:每个对象用一个128位GUID来标识,称为CLSID(class identifier,类标识符或类ID),用CLSID标识对象可以保证(概率意义上)在全球范围内的唯一性。只要系统中含有这类COM对象的信息 && 包括COM对象所在的模块文件(DLL或EXE文件) && COM对象在代码中的入口点,客户程序就可以由此CLSID来创建COM对象。
标志接口:COM模型中,对象本身对客户来说是不可见,客户只能通过接口请求服务。每一个接口都由一个128位的全局唯一标识符(GUID,Globally Unique Identifier)来标识,客户通过GUID获得接口的指针,通过接口指针调用相应的成员函数。
使用服务:客户成功创建对象后,它得到一个指向对象某个接口的指针,因为COM对象至少实现一个接口,所以客户就可以调用该接口提供的所有服务。
COM库:一般不在应用程序层实现,而在操作系统层次上实现,因此一个操作系统只有一个COM库实现,而且COM库的实现必须依赖于具体的系统平台,尤其是系统底层的一些标准。库可以保证所有的组件按照统一的方式进行交互操作,使我们在编写COM应用时,可以不编写为进行COM通信而必需的大量基础代码,而直接利用COM库提供的API进行编程,加快了开发速度。
COM对象特性:封装性 && 重用性 && 多态性。但这种多态性需要通过COM对象所具有的接口才能体现出来,就像C++对象的多态性需要通过其(virtual)函数才能体现一样。
COM接口规范:接口是包含了一组函数的数据结构,这组成员函数是组件对象暴露出来的所有信息,通过这组数据结构,客户代码可以调用组件对象的功能。客户程序用一个指向接口数据机构的指针来调用接口成员函数。接口指针实际上又指向另一个指针,这第二个指针指向一组函数,称为接口函数表(虚函数表),接口函数表中的每一项为4个字节长的函数指针,这与对象的具体实现连接起来,客户只要获得了接口指针,就可以调用到对象的实际功能。对于一个接口来说,他的虚函数表vtable是确定的,因此接口的成员函数个数是不变的,而且成员函数的先后顺序也是不变的;对于每个成员函数来说,其参数和返回值也是确定的。
接口描述语言:如何描述接口呢?COM接口描述语言是IDL,IDL接口描述语言不仅可用于定义COM接口,同时还定义了一些常用的数据类型,也可以描述自定义的数据结构。对于接口成员函数,我们可以指定每个参数的类型,输入输出特性,甚至支持可变长度的数组的描述。IDL支持指针类型,与C/C++很类似。Microsoft Visual C++提供了MIDL工具,可以把IDL接口描述文件编译成C/C++兼容的接口描述头文件(.h),从而可以把一种奇怪的东东IDL描述的接口转化C++形式:
IUnknown的定义(IDL): interface IUnknown { HRESULT QueryInterface([in] REFIID iid, [out] void **ppv); ULONG AddRef(void); ULONG Release(void); } IUnknown的定义(C++): class IUnknown { Public: virtual HRESULT _stdcall QueryInterface([in] REFIID iid, [out] void **ppv)=0; virtual ULONG _stdcall AddRef(void)=0; virtual ULONG _stdcall Release(void)=0; }
IUnKnown是:一个接口,所有COM接口都继承IUnKnown。定义在WIN32 SDK中的UNKNWN头文件中。
QueryInterface是:IUnKnown的成员函数,客户可以通过此函数来查询组件是否支持某个特定的接口,返回一个指向组件支持的接口的指针,没有找到则返回NULL。用QueryInterface将组件抽象成由多个相互独立的接口构成的集合。
组件的生命期:这一点是通过对接口的引用计数实现的。客户并不能直接控制组件的生命期,当使用完一个接口而要用组件的另一个接口时,是不能将该组件释放的,对组件的释放可以由组件在客户使用完所有组件之后自己完成。AddRef和Release实现的是一种名为引用计数的内存管理技术,当客户从组件获得一个接口时,此引用数值将增加;当使用完某个接口时,组件的引用计数值将减1;当引用计数值为0时组件可以将自己从内存中删除。AddRef和Release可以增加和减少这一计数值。
进程内组件:进程内组件和客户程序运行在同一个进程地址空间中,所以一旦客户程序与组件程序建立起通信关系之后,客户程序得到的接口指针指向组件程序中接口的vtable,这vtable包含了所有成员函数地址,客户代码可以直接调用这些成员函数,所以效率非常高。DLL程序是在运行时刻被客户装入到内存中的,所以DLL模块本身也是独立的,它并不依赖于客户程序。
操作DLL程序:有三个系统函数,LoadLibrary && GetProcAddress && FreeLibrary。一般地,对于DLL程序的使用过程按照这样的步骤进行:
首先,客户程序使用LoadLibrary函数装入DLL,该函数返回模块的实例句柄,供以后操作该模块使用。
然后,客户程序可以调用GetProcAddress函数获得DLL中引出的函数的地址,我们既可以按函数的序号(在DEF文件中指定)也可以按函数的名字来获取引出函数的地址,因为客户程序和DLL程序在相同的内存地址空间中,所以客户程序可以直接调用这些引出函数。
最后,FreeLibrary,把DLL程序卸出内存,以便释放资源。
COM组件不是:COM组件不是DLL,COM只是利用DLL来给组件提供动态链接的能力;COM组件不是一个API函数集;COM组件不是类。
创建COM对象:客户程序通过调用CoCreateInstance来创建对象的一个实例,并向想要创建的对象传送一个CLSID。
DLL:是一种程序动态调用以及连接的技术,他和组件是完全两种不同的概念,很多组件都采用这种技术作为自身的连接方式。
IDispatch:IDispatch的存在是因为有些语言不支持虚函数表vtable,比如vb,asp等,它的主要作用是”接收一个函数的名称并执行它“,IDispatch有几个函数:
GetIDsOfNames:取一个函数的名称并返回其调度ID,或称DISPID
Invoke:可以将DISPID作为函数数组指针的索引
附: