COM应用和原理摘自大神


为了把应用系统和组件程序连接起来,又能使它们协同工作,最简单的做法就是先定义一组查字典的函数,而且这组函数尽可能一般化,不要加入特定的与字典库相关的知识。


函数
 功能说明
 
Initialize
 初始化
 
LoadLibrary
 装入字典库
 
InsertWord
 插入一个单词
 
DeleteWord
 删除一个单词
 
LookupWord
 查找单词
 
RestoreLibrary
 把内存中的字典库存入指定的文件中
 
FreeLibrary
 释放字典库
 

平面型的API接口层可以很好地把两个程序连接起来,但存在以下一些问题:

(1) 当API函数非常多时,使用会非常不方便,需要对函数进行组织。

(2) API函数需要标准化,按照统一的调用方式进行处理,以适应不同的语言编程实现。参数的传递顺序,参数类型,寒暑返回处理都需要标准化。

COM定义了一套完整的接口规范,不仅可以弥补以上API作为组件借口的不足,还充分发挥了组件对象的优势,并实现了组件对象的多态性。

接口定义和标识 
从技术上讲,接口是包含了一组函数的数据结构,通过这组数据结构,客户代码可以调用组件对象的功能。接口定义了一组成员函数,这组成员函数是组件对象暴露出来的所有信息,客户程序利用这些函数或的组件对象的服务。


客户程序用一个指向接口数据机构的指针来调用接口成员函数。接口指针实际上又指向另一个指针,这第二个指针指向一组函数,称为接口函数表(虚函数表),接口函数表中每一项为4个字节长的函数指针,每个函数指针与对象的具体实现连接起来。通过这种方式,客户只要获得了接口指针,就可以调用到对象的实际功能。


对于一个接口来说,他的虚函数表vtable是确定的,因此接口的成员函数个数是不变的,而且成员函数的先后顺序也是不变的;对于每个成员函数来说,其参数和返回值也是确定的。


在一个接口的定义中,所有这些信息都必须在二进制一级确定,不管什么语言,只要能支持这样的内存结构描述,也就是能够支持“structure“或“record“类型,并且这种类型能够包含双重的指向函数指针表的成员,则它就可以支持接口的描述,从而可以用于编写COM组件或者使用COM组件。

接口描述语言IDL

COM规范在采用OSF的DCE规范描述远程调用接口IDL的基础上,进行扩展形成了COM接口的描述语言。

COM规范使用的IDL接口描述语言不仅可用于定义COM接口,同时还定义了一些常用的数据类型,也可以描述自定义的数据结构,对于接口成员函数,我们可以指定每个参数的类型,输入输出特性,甚至支持可变长度的数组的描述。IDL支持指针类型,与C/C++很类似。

Microsoft Visual C++提供了MIDL工具,可以把IDL接口描述文件编译成C/C++兼容的接口描述头文件(.h)。

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;
}

进程内组件

因为进程内组件和客户程序运行在同一个进程地址空间中,所以一旦客户程序与组件程序建立起通信关系之后,客户程序得到的接口指针指向组件程序中接口的vtable,这个vtable包含了所有成员函数地址,客户代码可以直接调用这些成员函数,所以其效率非常高。

因为DLL程序是在运行时刻被客户装入到内存中的,所以DLL模块本身也是独立的,它并不依赖于客户程序。

在C++语言中,为了使编制的DLL程序更为通用,一般指定DLL的引出函数使用_stdcall调用习惯,如果使用了_cdecl调用习惯,则有些编程语言环境就不能使用这些DLL程序。C++编译器为DLL程序的每个引出函数生成了一个修饰名,这些修饰名对于不同的编译器并不兼容,因此,从通用性角度出发,我们在每个函数定义前加上extern ?C“ 说明符。在Visual C++ 开发环境中,下面的说明语句可以很好的说明一个引出函数:

extern ? C“ int _stdcall MyFunction(int n);

为了编制DLL程序,我们可以按照这样的步骤:

(1) 创建一个DLL工程

(2) 对每个引出函数,使用extern ? C“说明符,以及_stdcall修饰符,如上面对MyFunction函数的说明。

(3) 按照传统的编程方法,我们还应该编写一个DEF文件,用来描述DLL程序的模块信息。在Win32平台上,我们可以不使用DEF文件,而是直接在函数说明时使用_declspec(dllexport)说明符,例如:

extern ? C“_declspec(dllexport) int _stdcall MyFunction(int n);

按照这样的方法建立起来的DLL模块可以被其他程序调用,因为C++连接器会把所有引出函数的信息连接到最终的目标代码中。


从客户程序一方来看,有三个系统函数可用于操作DLL程序,LoadLibrary, GetProcAddress, 和FreeLibrary。


一般地,对于DLL程序的使用过程按照这样的步骤进行:

首先,客户程序使用LoadLibrary函数装入DLL,该函数返回模块的实例句柄,供以后操作该模块使用。

然后,客户程序可以调用GetProcAddress函数获得DLL中引出的函数的地址,我们既可以按函数的序号(在DEF文件中指定)也可以按函数的名字来获取引出函数的地址,因为客户程序和DLL程序在相同的内存地址空间中,所以客户程序可以直接调用这些引出函数。

最后FreeLibrary,把DLL程序卸出内存,以便释放资源。


说明:

(1) DLL程序不仅可以引出函数,也可以引出全局变量,因为客户程序和DLL程序在同一个地址空间,所以,把DLL中的全局变量引出到客户程序中是有意义的。引用的方法并不复杂,或者把变量名放到DEF文件的EXPORTS部分,并加上DATA选项; 或者在变量说明前面加上_declspec(dllexport)说明符。

(2) DumpBin 通过/Exports选项可以列出DLL程序中的所有被引出的信息。

(3) 客户程序本身也可以是一个DLL程序,但它一定先被装入到进程空间中,以便可以调用系统函数操作作为服务程序的DLL模块。

进程外组件 
因为进程外组件程序和客户程序位于不同的进程空间之中,他们使用不同的地址空间,所以组件和客户之间的通信必须跨越进程边界,这就涉及到以下一些问题:

(1) 一个进程如何调用另外一个进程中的函数

(2) 参数如何从一个进程被传递到另外一个进程中

Windows平台上,在不同进程之间进行通信的办法很多,包括DDE, named pipe,或者共享内存等等,COM采用了LPC(Local Procedure Call)和RPC(Remote Procedure Call)

RegEdit可检查CLSID子键下的COM对象(63页) 
Microsoft Visual C++提供OleView.exe,可列出当前机器上的所有类别信息,以及每一种类别下的组件对象列表。

RegSvr32 D:\DicComp\DictComp.dll

RegSvr32 /u D:\DicComp\DictComp.dll

DLL组件必须有DllRegisterServer和DllUnregisterServer两个用于注册的入口函数,才能用RegSvr32注册。

COM规定,支持自注册的进程外组件必须支持两个命令行参数/RegServer和/UnregServer,以便完成注册或注销操作。 
Class Factory

实际上,客户程序并不直接调用组件程序的引出函数,它调用COM库的函数进行组件对象的创建工作,COM库的创建函数根据注册表的信息调用组件程序的入口函数来创建组件对象。组件程序需要提供一个标准的入口函数DLLGetObjectClass,用于提供本组程序的组件信息。

Class Factory和DLLGetObjectClass函数 
类厂是COM对象的生产基地,COM库通过类厂创建COM对象; 对应每一个COM类,有一个类厂专门用于该COM类的对象创操作。类厂本身也是一个COM对象,它支持一个特殊的接口:IClassFactory,其定义如下:

Class IClassFactory : public IUnknown

{
virtual HRESULT _stdcall CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void **ppv) = 0;
virtual HRESULT _stdcall LockServer(BOOL bLock) = 0;

};

接口IClassFactory有一个重要的成员函数CreateInstance,用于创建对应的COM对象。因为每个类厂之针对特定的COM类对象,所以CreateInstance成员函数知道该创建什么样的COM对象。在CreateInstance成员函数的参数中,第一个参数pUnknownOuter用于对象被聚合的情形,没有聚合设成NULL。IClassFactory的另一个成员函数LockServer用于控制组建的生存周期。

因为类厂本身也是个COM对象,它被用于其它COM对象的创建过程,那么类厂对象又由谁来创建呢?答案是DLLGetClassObject引出函数。DLLGetClassObject函数并不是COM库的函数,而是由组件程序实现的引出函数,我们先看一下DLLGetClassObject函数的原型:

HRESULT DLLGetClassObject(const CLSID& clsid,
Const IID& iid,
(void **) ppv
);

COM库在接到对象创建的指令后,它要调进程内组件的DLLGetClassObject函数,由该函数创类厂对象,并返回类厂对象的接口指针,COM库或者客户一旦有了类厂的接口指针,它们就可以通过类厂接口IClassFactory的成员函数CreateInstance创建相应的COM对象。

COM库与类厂的交互(67页) 
在COM库中,有三个API函数可用于对象的创建,它们分别是CoGetClassObject, CoCreateInstance和CoCreateInstanceEx。通常情况下,客户程序调用其中之一完成对象的创建,并返回对象的初始接口指针。COM库与类厂也通过这三个函数进行交互。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值