首页 理论教育 VisualC++高级编程技术与实例:COM组件的转换方法

VisualC++高级编程技术与实例:COM组件的转换方法

时间:2023-11-16 理论教育 版权反馈
【摘要】:前面小节的例程说明了DLL标准化之后COM创建对象的过程,其中的char_set类还不是一个真正的COM组件类,同样,它的实例也不是COM对象。按照COM规范,一个COM对象可以实现多个接口,这些接口都可以用一致的方式查询。假如我们的组件对象被多个客户同时使用,所有的客户程序访问同一个对象。

VisualC++高级编程技术与实例:COM组件的转换方法

前面小节的例程说明了DLL标准化之后COM创建对象的过程,其中的char_set类还不是一个真正的COM组件类,同样,它的实例也不是COM对象。要使char_set类的对象成为真正的COM对象,还要让这个类遵守一些约定(标准化),并且,上面的DLL还需要进一步处理。

1. 组件类共同的抽象基类IUnknown

既然COM组件通过接口提供服务,那么客户是如何知道提供了哪些服务呢?按照COM规范,一个COM对象可以实现多个接口,这些接口都可以用一致的方式查询。当客户创建了COM对象之后,创建函数总会返回一个接口指针,该接口指针指向的vtable中有一个名为QueryInterface的函数,可以通过初始的指针调用这个函数获得对象所支持的任何一个接口的指针。让我们来看一下QueryInterface函数的说明;

HRESULT QueryInterface(REFIID riid,void** ppObject);

组件对象的任何一个接口都有一个惟一的IID,调用QueryInterface函数时,只要指明需要接口正确的IID(参数riid),就可以得到该接口的指针ppObject。另外,COM规范中关于接口还有一个比较重要的规则:接口查询的互操作性。遵守这个规则,不论从哪个接口出发,总可以查询到其它的任何一个接口。

因为组件总是作为一个DLL或EXE发布,所以共享性是它的一个特性。假如我们的组件对象被多个客户同时使用,所有的客户程序访问同一个对象。如果一个客户调用Release释放了该对象,那么其它的客户将对一个不存在的对象进行操作。问题出现了:如何控制它在何时释放呢?这个问题的解决很简单:给对象增加一个引用计数变量,用于记录当前有多少个指针(客户)正在指向自己,当用户调用Release函数,不再直接释放对象,而是引用计数减1;相反,当有一个客户查询了该接口,即有一个指针指向对象时,引用计数加1。只有当引用计数变成0。表明没有客户使用了,对象才可以释放。

因此,每个COM对象都需要两个函数用于管理引用计数,在COM规范中,它们是:

ULONG AddRef();和ULONG Release();

这两个函数不直接创建和删除对象,主要用于加减引用计数。AddRef、Release和QueryInterface三个函数合在一起,构成标准接口类IUnknown,这个抽象基类是所有组件对象的类都必须要继承的。

2. 类厂共同的接口类IClassFactory

类厂标准化之后,形成了类厂类的抽象基类IClassFactory。由于类厂也是一个组件对象,所以类厂接口也由IUnknown派生,下面是类厂接口的标准化定义:

其中,LockServer函数用于控制组件的生存周期。为了解决组件对象释放后类厂接口指针有可能引起非法访问错误的问题,类厂提供了这个机制:在需要时,把组件对象锁定在内存中。

3. 多接口

一般来说,比较复杂的组件对象都提供一个以上的接口。比如:一个学校教务管理的组件可以有两个接口,一个接口提供给学生,一个接口提供给教师。对于这样的多接口组件对象,它的实现很简单,把相应的功能函数按类别分开,封装到不同的抽象基类中,然后使用多继承或者和多继承相类似的方法,把这些抽象基类汇集成一个派生类,每一个接口(基类)对应一个惟一的IID,通过派生类实现的QueryInterface函数可以任意地查询并访问这些接口。

4. 组件DLL的标准化管理

读者已经看到,上一节的例程中我们使用手工的方式修改注册表来完成组件的注册。COM规范要求:组件应能够自动完成注册,就是说要提供组件自己注册过程实现的代码。这样做可以简化对象的注册和反注册(卸载)。注册管理代码要在两个标准化导出函数DllRegisterServer和DllUnregisterServer中实现。

Windows提供的用于注册的工具RegSvr32.exe它本身实际上不进行任何注册动作,而是调用DLL的DllRegisterServer和DllUnregisterServer来完成DLL的注册与反注册。

应用程序在执行时通过COM库动态地加载COM组件,与之类似,组件的卸载(从内存中删掉)也要交给COM库来完成。COM库中的函数调用DLL的另一个标准导出函数DllCanUnloadNow,根据该函数返回值来判断是否可以卸载DLL。

5. 实现真正COM组件的例程

此处的例程是在前一节例程的基础上进行上述四个方面的处理,让我们的C++对象变成一个真正意义上的COM组件对象。

(1)新建DLL空项目。

创建DLL空项目REALCOM,拷贝并添加CS_NEARCOM的四个文件Ichar_set.h、char_set.h、char_set.cpp和char_set.def到本项目中。(www.xing528.com)

(2)添加多接口支持。

假如有的客户只需要操作char_set类的字符长度,而有的客户只需要操作该类的字符。就可以为char_set类再增加两个接口(抽象基类),它们都要从IUnknown派生。COM要求组件类的接口函数都要遵守__stdcall调用约定,IUnknown和IClassFactory中的虚函数都是如此。综上所述,我们修改接口文件Ichar_set.h和组件类的声明文件char_set.h,如程序清单8-13所示。

程序清单8-13 接口文件Ichar_set.h和组件类的声明文件char_set.h

(3)修改组件对象的实现程序。

在这里,我们要重点设计QueryInterface函数以及和引用计数相关的函数。实际上,实现它们各自功能的代码都很简单。另外,为了实现类厂的LockServer函数,要增加一个全局变量g_RefCount。下面是char_set.cpp的修改后的代码(程序清单8-14)。

程序清单8-14 组件类和类厂的实现文件char_set.cpp

(4)为DLL添加标准导出函数。

COM组件的注册管理函数和卸载函数都要在这里实现,这些标准函数的名称是:DllRegisterServer、DllUnregisterServer和DllCanUnloadNow。其中,前两个函数用到了关于注册表的Windows API函数,读者可参阅第1章。修改char_set.cpp文件,在文件的尾部添加的代码如程序清单8-15所示。

程序清单8-15 组件DLL标准导出函数

(5)修改DLL模块的定义文件。

打开char_set.def模块定义文件,添加新增三个函数的导出说明。修改后的DEF文件如下所示:

(6)创建并注册DLL。

编译本项目程序生成REALCOM.DLL,用命令行方式运行Regsvr32.exe程序。即打开“开始”菜单选择“运行…”,在弹出的小对话框中录入:

Regsvr32 " <PATH>\REALCOM.DLL"

其中,<PATH>表示你的DLL所在的路径。运行后,Regsvr32会调用组件程序的DllRegisterServer函数完成组件的注册工作。

(7)创建客户应用程序。

建立一个控制台类型的空客户项目CLIENT4,拷贝添加REALCOM中的Ichar_set.h文件到CLIENT4项目中,然后编辑一个客户程序Client4.cpp,如程序清单8-16所示。

程序清单8-16 客户程序Client4.cpp

细心的读者已经发现:这个客户程序和CLIENT3的客户程序几乎一模一样,就差一个COM库函数CoFreeUnusedLibraries调用语句(该语句可以省略,不影响本程序的执行)。这个函数调用DllCanUnloadNow函数判断组件是否可以卸载,如果可以卸载,就接着调用FreeLibrary函数从内存中卸载DLL。实际上,从本例程的开发过程就可以看出COM组件的好处:在使用组件的容器应用程序中,只要容器需要的接口不发生变化,组件可以进行对其接口实现方面的修改,或者增加接口进行组件升级的修改!

此时此刻,我们已经成功地把一个C++对象逐步改造成了一个真正的COM组件对象。现在,读者还认为COM很神秘吗?

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈