COM编程技术基础.pdf

上传人:qwe****56 文档编号:70019655 上传时间:2023-01-14 格式:PDF 页数:21 大小:247.75KB
返回 下载 相关 举报
COM编程技术基础.pdf_第1页
第1页 / 共21页
COM编程技术基础.pdf_第2页
第2页 / 共21页
点击查看更多>>
资源描述

《COM编程技术基础.pdf》由会员分享,可在线阅读,更多相关《COM编程技术基础.pdf(21页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、COM 编程技术基础 一、前言 所谓 COM(Componet Object Model,组件对象模型),是一种说明如何建立可动态互变组件的规范,此规范提供了为保证能够互操作,客户和组件应遵循的一些二进制和网络标准。通过这种标准将可以在任意两个组件之间进行通信而不用考虑其所处的操作环境是否相同、使用的开发语言是否一致以及是否运行于同一台计算机。显然,在 COM 规范下将能够以高度灵活的编程手段来开发、维护应用程序。可以将一个单独的复杂程序划分为多个独立的模块进行开发,这里的每一个独立模块都是一个自给自足的组件,可以采取不同的开发语言去设计每一个组件。在运行时将这些组件通过接口组装起来以形成所需

2、要的应用程序。构成应用程序的每一个组件都可以在不影响其他组件的前提下被升级。这里所说的组件是特指在二进制级别上进行集成和重用而能够被独立生产获得和配置的软件单元。COM 规范所描述的即是如何编写组件,遵循 COM 标准的任何一个组件都是可以被用来组合成应用程序的。至于对组件采取的是何种编程语言则是无关紧要的,可以自由选取。作为一个真正意义上的组件,应具备如下特征:1)实现了对开发语言的封装。2)以二进制形式发布。3)能够在不妨碍已有用户的情况下被升级。4)在网络上的位置必须能够被透明的重新分配。这些特征使 COM 组件具有很好的可重用性,这种可重用性与 DLL 一样都是建立在二进制基础上的代码

3、重用。但是 COM 在多个方面的表现均要比 DLL 的重用方式好的多。例如,在 DLL 中存在的函数重名问题、各编译器对 C+函数名称修饰的不兼容问题、路径问题以及与可执行程序的依赖性问题等在 COM 中通过使用虚函数表、查找注册表等手段均被很好的解决。其实 COM 组件在发布形式上本身就包扩 DLL,只不过通过制订复杂的 COM规范,使 COM 本身的机制改变了重用的方法,能够以一种新的方法来利用 DLL 并克服 DLL本身所固有的一些缺陷,从而实现了更高层次的重用。客户程序在与 COM 组件进行交互时,只需知道与哪个 COM 对象进行交互即可而不必关心组件模块的具体名称和位置,即 COM

4、对象的位置对客户是透明的。客户将通过一个 128位的全局标识符(globally unique identifier,GUID)完成对象的创建和初始化工作。对于COM 对象,此全局标识符也被称作 CLSID(class identifier,类标识符)。采用 GUID 对 COM对象进行标识的目的是为了保证对该对象标识的全球唯一性,因此若用人工构造此 GUID 将存在与已有 COM 对象的 GUID 发生冲突的可能。通常是采用 VC+附带的两个工具UUIDGen.exe 和 GUIDGen.exe(如图 1 所示)来根据一定的算法产生出唯一的 GUID 值。这两个工具可以在 Visual St

5、udio 安装目录下的CommonTools目录下找到。如果需要在程序中通过代码来获取,也可以使用 COM 库提供的 CoCreateGuid()API函数。每一个注册了的 COM 对象在系统注册表的 HKEY_CLASSES_ROOTCLSID 子键下均对应一个以 CLSID 的字符串形式命名的子键。在此子键下,通过 COM 库可以得到所需要的信息并完成对象的创建。在 Windows 环境下,除了 CLSID 可以唯一标识一个 COM 对象外,也支持通过组件对象名对 COM 对象的标识。此标识信息称为 ProgID(program identifier,程序标识符)。通常在以 CLSID 的

6、字符串形式命名的子键下存在有 ProgID 子键,而在 HKEY_CLASSES_ROOT 键下可以找到以此子键键值命名的子键,该子键下亦包含有CLSID子键,通过ProgID子键的CLSID值和CLSID子键的ProgID值可以将CLSID与ProgID建立起联系。在程序中也可以通过 CLSIDFromProgID()和 ProgIDFromCLSID()进行相互转换。图 1 使用 GUIDGen 创建 GUID 二、COM 接口与 COM 组件 COM 接口是 COM 规范中最重要的部分,COM 规范的核心内容就是对接口的定义,甚至可以说“在 COM 中接口就是一切”。组件与组件之间、组件

7、与客户之间都要通过接口进行交互。接口成员函数将负责为客户或其他组件提供服务。与标识 COM 对象的 CLSID 类似,每一个 COM 接口也使用一个 GUID 来进行标识,该标识也被称为 IID(interface identifier,接口标识符)。COM 接口实际限定了组件与使用该组件的客户程序或其他组件所能进行的交互方式,任何一个具备相同接口的组件都可对此组件进行相对于其他组件透明的替换。只要接口不发生变化,就可以在不影响整个由组件构成的系统的情况下自由的更换组件。通常在程序设计阶段需要将接口设计的尽可能完美,以减少在开发阶段对 COM 接口的更改。尽管如此,在实际应用中是很难做到这一点

8、的,往往需要在现有接口基础上对其做进一步的发展。与 C+中对类的继承有些类似,对 COM 接口的发展也可以通过接口继承来实现。但是 COM 接口的继承只能是单继承而不允许从多个基接口进行派生,而且派生接口只是继承了对基接口成员函数的说明而没有继承其实现。interface IX/IX 接口 virtual void _stdcall Func1()=0;virtual void _stdcall Func2()=0;interface IY/IY 接口 virtual void _stdcall Func3()=0;virtual void _stdcall Func4()=0;class C

9、ObjectA/组件 A public:/抽象基类 IX 的实现 virtual void Func1()coutFunc1endl;virtual void Func2()coutFunc2endl;/抽象基类 IY 的实现 virtual void Func3()coutFunc3endl;virtual void Func4()coutFunc4QueryInterface(IID_IX,(void*)&pIX);if(SUCCEEDED(hResult)pIX-Func1();由于 QueryInterface()过于灵活,为避免由此引发的冲突在 COM 规范中定义了QueryInte

10、rface()所有实现都必须遵循的一些规则:(1)过同一对象各个接口指针所查询得到的 IUnknown 接口指针必须是指向同一个IUnknown 接口的。即,IUnknow 接口的唯一性。(2)如果某接口曾经被成功查询过,那么此后任何时间对该接口的查询也必定会成功。即,接口与查询时间的无关性。(3)对于已经获取到的接口仍可对其进行再次查询,并且必定会成功。即,接口的自反性。(4)客户能够从任何接口查询到另外一个接口,而且能够返回到起始接口。即,接口的对称性。(5)如果能够从某接口获取到某特定接口,那么从任意接口都可以得到此接口。即,接口的传递性。IUnknown 接口的另两个成员函数 AddR

11、ef()和 Release()对对象的生存期进行了控制。每个 COM 对象都记录有一个引用计数,该引用计数表示了当前引用了此 COM 对象的有效指针的个数。AddRef()和 Release()实现的即是这种引用计数的内存管理技术:引用计数初始为 0,客户每得到一个指向此对象的接口指针即通过 AddRef()将引用计数加 1;在每用完此接口指针后,调用 Release()函数将引用计数减 1。如果引用计数减到 0,则从内存卸载掉此 COM 对象。关于引用计数的使用,在 COM 规范中也设置了以下几条简单的规则:(1)任何能够返回接口指针的函数(如 QreryInterface()、Create

12、Instance()等)在返回接口指针之前,必须用相应的指针调用 AddRef()函数。(2)在使用完任何一个接口后,应及时调用该接口的 Release()函数。(3)在进行接口指针赋值操作后,应调用 AddRef()函数。COM 组件的创建可以通过 CoCreateInstance()函数来完成,函数原型为:HRESULT _stdcall CoCreateInstace(const CLSID&clsid,IUnknown*pIUnknownOuter,DWORD dwClsContext,const IID&iid,void*ppv);函数参数 clsid 是要创建组件的 CLSID,p

13、IUnknownOuter 用于聚合组件,如果不使用可以设置为 NULL。参数 dwClsContext 则限定了所创建组件的执行上下文。最后两个参数 iid和 ppv 则分别为要使用接口的 IID 和返回得到的接口指针。在使用时只需将 CLSID、IID 等作为参数传入即可创建相应的组件并从输出参数 ppv 得到所请求接口的指针。如果函数是直接创建组件的,那么在函数返回时组件将创建完毕,这样客户将无法对组件的创建过程进行任何干预,灵活性太差。因此,CoCreateInstance()在函数内部实现中通过调用CoGetClassObject()函数先创建一种专门用来创建组件的组件来解决此问题。

14、这种用途的组件被称为类厂(class factory)。类厂所支持的用以创建组件的接口是 IClassFactory,该接口从 IUnknown 派生,并具有两个自己的接口成员函数 CreateInstance()和 LockServer()。这两个成员函数分别用于创建 COM 组件对象和控制组件的生存期。下面先给出 CreateInstance()的函数声明:HRESULT _stdcall CreateInstance(IUnknown*pIUnknownOuter,const IID&iid,void*ppv);可以看出,这个用于创建组件对象的 CreateInstance()函数并未包

15、含一个用来接受CLSID 的参数,显然该函数将只能创建同某个 CLSID 相应的组件。对于一个类厂,由于只能通过 CreateInstance()函数去创建组件,因此只能创建与某个特定 CLSID 相应的组件。创建类厂的 CoGetClassObject()函数将接收一个 CLSID 作为参数并返回指向类厂对象 IClassFactory 接口的指针。客户将可以通过此指针来创建所需要的组件并返回某接口的指针。通过此指针,客户将可以直接调用新创建的 COM 对象接口的成员函数,从而获得COM 对象的所有服务。在用 CoGetClassObject()创建类厂对象时,如果 COM 对象是进程内组件

16、(组件与客户处于同一进程地址空间,通常多以 DLL 形式存在),CoGetClassObject()将调用 DLL 模块的 DllGetClassObject()引出函数并把 clsid、iid 和 ppv 等参数传递进去以创建类厂,并返回类厂对象的接口指针。如果 COM 对象是进程外组件(拥有独立的进程地址空间,通常多以 EXE 形式存在),则 CoGetClassObject()将 要 首 先 启 动 组 件 进 程,并 一 直 等 待 到 组 件 进 程 通 过CoRegisterClassObject()函数将类厂注册到 COM 后,才会返回 COM 中相应的类厂信息。一旦组件进程退出

17、,此注册的类厂对象也就不再有效,需调用 CoRevokeClassObject()函数予以通知。图 4 展示了通过类厂创建组件的过程:客户程序对 COM 组件的调用主要分对进程内组件调用和进程外调用两种情况。在具体过程上却并没有什么太大的区别。为了能够使用 COM 库提供的 API 函数,首先要用CoInitialize()初始化 COM 库。图 4 组件的创建过程 虽然通过 CLSID 和 ProgID 都可以标识一个组件,但 ProgID 显然要比 CLSID 更易于理解和使用,因此通常很少直接使用 CLSID,而是通过使用 CLSIDFromProgID(),根据 ProgID得到组件的

18、 CLSID。进而以此返回的 CLSID 作为参数去调用 CoGetClassObject()以创建类厂对象并返回类厂接口指针。通过该指针调用类厂对象的 CreateInstance()接口成员函数,执行结果将创建与 CLSID 相应的组件对象并返回 IUnknown 接口指针。通过此接口的QueryInterface()成员函数将能够进一步获过程将是隐含进行的,使用更为简单。取组件的其他接口指针,从而使用组件提供的各种服务。最后,通过 Release()函数释放接口指针。如果使用的进程内组件,在调用 CoUninitialize()函数释放 COM 库资源之前,应首先调用 CoFreeUnu

19、sedLibraries()将其从内存卸载。由于在 CoCreateInstance()函数内部实现了对 CoGetClassObject()的调用并一直完成了类厂对象接口函数对组件的创建和类厂对象的释放,因此对于客户,类厂的全部使用。在 ActiveX 文档服务器中的 IOleDocument 接口使一个文档对象能够与其包容器进行通信,并用其数据去创建视图,该接口也可以使一个文档对象能够枚举其视图并为包容器提供相关信息,如是否支持多视等。IOleDocumentView 接口则使一个包容器程序能够通过文档对象的支持而与每一个视图进行通信。IOleCommandTarget 接口可以使服务器对

20、象及其包容器程序分发命令。IPrint 接口则可以使任意的复合文档和特定的活动文档能够支持打印。在 ActiveX 文档包容器中实现的 IOleDocumentSite 接口能够使一个已经作为文档对象实现的文档在现场激活对象时绕过通常的激活次序,并直接指示其客户站点作为一个文档对象而将其激活。具有这种能力的客户站点也被称为文档站点。包容器程序需要为每一个文档对象提供一个相关的文档站点,这些站点对象为每一个活动文档的视图实现了一个独立的文档视图站点对象。相比之下,ActiveX 控件可以说是在所有 COM 应用中使用最为广泛的一种 COM 组件。这种 COM 组件集成了 COM 的各种应用基础,

21、如 OLE 文档、自动化、类型库等。ActiveX控件通常以 DLL 或 OCX 形式存在,而且只能在包容器程序中使用而不可独立运行,这与ActiveX 文档是不一样的。ActiveX 控件是一种实现了一系列特定接口而使其在使用和外观上更象一个控件的COM 组件。ActiveX 控件这种技术涉及到了几乎所有的 COM 和 OLE 的技术精华,如可链接对象、统一数据传输、OLE 文档、属性页、永久存储以及 OLE 自动化等。ActiveX 控件作为基本的界面单元,必须拥有自己的属性和方法以适合不同特点的程序和向包容器程序提供功能服务,其属性和方法均由自动化服务的 IDispatch 接口来支持。

22、除了属性和方法外,ActiveX 控件还具有区别于自动化服务的一种特性-事件。事件指的是从控件发送给其包容程序的一种通知。与窗口控件通过发送消息通知其拥有者类似,ActiveX控件是通过触发事件来通知其包容器的。事件的触发通常是通过控件包容器提供的IDispatch接口来调用自动化对象的方法来实现的。在设计 ActiveX 控件时就应当考虑控件可能会发生哪些事件以及包容器程序将会对其中的哪些事件感兴趣并将这些事件包含进来。ActiveX 控件与自动化服务的另一个不同之处在于其方法、属性和事件均有自定义(custom)和库存(stock)这两种不同的类型。自定义的方法和属性也就是是普通的自动化方

23、法和属性,自定义事件则是自己选取名字和 Dispatch ID 的事件。而所谓的库存方法、属性和事件则是使用了 ActiveX 控件规定了名字和 Dispatch ID 的标准方法、属性和事件。样例目标 欲给一个公司做一个信息管理系统,也就是公司中所有部门的信息可以被输入电脑,并可进行分布式查询,即总经理可随时查询最新的订单情况和出货情况。由于使用 COM 作为此信息管理系统的基架,可以很容易的解决分布式问题,并且由于COM 对安全的包装,使得提供访问控制也变得容易。下面先说明 COM 提供的编程思想,再以此编程思想设计各接口。三、COM 编程模型 见过不少这种说法:“COM 是更加地面向对象

24、,封装地更彻底”。这里要纠正这种错误的思想,虽然可以说对,但是是错误的应用。这就好像牛刀可以杀鸡,但并不应该被说对。面向对象编程思想是一种思想,指导如何设计程序架构的。其主打思想就是将被操作数据看成一个个对象。而所谓的对象就是具有状态,并对外提供了接口以暴露其可以提供的服务。其是状态和功能通过语义的混合体。其和日常生活很像,比如电视机就既提供了服务搜台,又提供了状态哪个频道是哪个台。因此在使用面向对象编程思想时会从对象的概念出发来定义数据结构,这和 COM完全不一样。COM 叫做组件对象模型,从名字看其异常明显地表示了最开始引号中的话的正确性,这是个误解。COM 最突出的贡献不是组件这个概念,

25、而是接口。接口表示功能的集合,其不是状态。与面向对象正好相反,其完全不看重对象的实现,甚至淡化对象这个概念,极力强调接口的概念,这在各本 COM 教科书中表现地很明显里面第一个讲的就是 IUnknown 接口,极力强调没有对象指针,只有接口指针。这看起来有点混乱,如果认为面向对象强调的是状态和功能的混合,COM 强调的就是功能集的集合。而类就是只实现了一个接口的 COM 组件(不包括 IUnknown),这从根本上说是 COM 的退化。因此当设计中的每个 COM 组件都只实现一个接口时,此时根本不是设计一个 COM 应用,只是在二进制代码级上应用 C+提供的编程思想而已。由于 COM 做地并不

26、是那么好,以至于会产生前面所说的误解。其强调功能的概念没有体现出来,而更表现为组件,以至于很容易认为组件是积木,而整个程序就是用不同的组件搭建的房子。这是对象级上的模块化编程。COM 不会设计到最后反而跑回老路上去。搭积木的重点是积木,是以积木来搭建房屋。而 COM 提供的并不是积木,是积木间衔接的形状,它主张在搭积木前先搭一个架子,不同的积木能放到架子上形成的不同的格子里,架子搭好后再根据架子上形成的格子的形状做积木,最后将积木放到架子上。而不是先做积木,然后根据积木搭房屋(这个比喻并不是非常准确)。思考这个问题:欲实现任务和任务管理器的功能,设计两个接口 ITask 和 ITaskMana

27、ger,考虑 ITask 的功能定义。其代表的是能够作用于任务上的功能,不是任务本身,因此其有如下两个方法:TerminateTask 和 GetProcessRateOfTask 以分别终止任务和得到任务进度。但是很明显,任务是需要启动的。如果按照面向对象的思想,在不考虑设计模式的情况下,很容易想到将任务的发起这个动作作为 ITask 中的一个方法:StartTask,这样 ITask 的实现者就是一个完全的任务,如果使用线程进行任务操作,其也就连那个线程的操作也一起包装起来,形成一个任务。这不是一个好的设计,ITask 是个接口,代表的是功能,不是对象。接口以为实现它的对象就可以照其定义进

28、行操作,因此 ITask 的实现者是可以被相当于任务一样的操作,而不是任务这个东西。前者具有更好的可扩展性,如可以通过按遥控器来操作东西,但那个东西不一定必须是电视机,而后者就一定要求其是电视机。因此 COM 里重点的不是组件,而是接口,这是一种可扩展性相当好的设计思想,可以称做面向接口编程思想。它本身是没有什么缺点的,但其实现方式由于使用对象的概念,则一定和状态关联,这在数据量很大时是不好的。如订单会很容易地就被设计成一个类,然后提供诸如订单结帐、提货等多种服务(即成员函数)。这里的问题就是订单如此之多,如果使用一个数组作为其容器显然性地问题严重,而链表更是应该判死刑。因此这里将订单设计成一

29、个类是很不明智的选择。对于此,应该专门仔细研究如何处理大数据量的技术,并将功能与状态拆开,然后数据变成原材料,而功能变成机器,通过流水线生产以提高效率。即面向对象是个人主义,当数据量大时,就需要分工合作来提高效率了。对于此,Microsoft 早已提供了 MTS 来帮助开发,其中提供的编程思想就是专门针对这种大数据量而设计的,提倡无状态组件,即状态和功能的分离,其对于开发大数据量的应用提供了非常好的支持。前面已经说明了 COM 提供的编程模型,下面就本样例说明如何设计接口。程序员考虑最多的事应该是如何偷懒,并且美其名曰“代码重用”,但现在又被更好听的名词所替代“具有可扩展性”。样例是一个公司的

30、信息管理系统,里面人事部门的信息处理和营销部门的将会千差万别,信息有完全不同的流程。因此是肯定需要一个一个编的。但它们能够被称做部门,就一定有共通的地方,这正是程序员最厉害的地方归纳能力,然后推演出其他东西以达到偷懒的目的。照前面的说法,由于数据量巨大,因此决定选择数据库而不是建立对象。由于各个部门没有什么同一性(其实还是有的,后叙),最后认为唯一相同的是同属一个公司旗下,故决定提供一个基本框架界面,其提供最基本的如错误处理、日志记录等功能,欲通过在同一个基架下以表现得各部门在同一公司旗下。基本框架需要提供错误日志的记录以方便系统的维护和查错;需要提供界面框架以容纳不同的部门组件的操作界面,即

31、需要提供菜单命令的提供,也出于 Windows 界面的想法而提供工具条和快捷键;需要提供任务管理器,因为在海量的数据中查找那么一两条信息不是瞬间的事,因此可能总经理发起了一个人事查找后,又发起一个订单查找或客户查找,但却由于等得不耐烦而终止了前面的人事查找;需要提供数据库系统的相关信息,以使得部门组件可以将数据存储到统一的地方,方便备份等管理。部门组件需要提供界面以进行信息操作(如录入、查找等),作为 Windows 界面,常规性地需要提供菜单、工具条的维护性操作(如命令的说明字符串的提供);需要提供任务执行进度,以提高操作者的忍耐限度。经过上面的决定,基本框架应有 4 个接口,而部门组件应有

32、 2 个接口。但请注意,一个接口表示一个功能集合,如果一个组件实现了一个接口就表示其所有功能都实现了,但 COM非常可惜地提供了 E_NOTIMPL 这个错误代码,因此导致了错误的接口设计里面的方法可以有未实现的。这个错误代码准确的说应该是为将来扩展而预留的,即方法中的某个参数代表功能的种类,如是画圆形还是画矩形,但其可以指定为画椭圆形,这种形状暂时不支持,但相信以后版本将会支持,这才是 E_NOTIMPL 的真正含义,却被错误的应用了,比如:上面提到的部门组件应该支持一个接口,其提供包含部门组件界面、菜单、工具条和快捷键的提供。完全有可能一个部门组件不使用工具条进行操作,完全使用一个对话视搞

33、定一切,那么当可怜的基本框架错误地以为其需要工具条的空闲处理而调用了相应方法时却得到一个 E_NOTIMPL 时,应该怎样?因此应该将一定会同时存在的功能归为一个接口,因此出于上面的考虑,应该再提供三个接口:快捷键处理、菜单处理、工具条处理。由于快捷键没有处理,只是获得即可,不像菜单还需提供菜单状态的处理等操作,所以无须快捷键处理的接口,因此部门组件应该具有 4 个接口,其中三个是可选的。上面的接口分工显得有些牵强,不过这只是粒度粗细的问题。如果愿意粗粒度,也可以说成基本框架只需一个接口,如果要细粒度,也可再定个工具条处理接口和菜单处理接口,这里就是见仁见智的地方了。但还是建议至少要保证接口中

34、的方法如果实现一个,则其他的逻辑上也都需实现。最后其 IDL 定义文件如下:import oaidl.idl;import ocidl.idl;/基本框架实现 IModuleSite,其提供基本的操作 object,uuid(1A201ABA-A669-4ac7-9DF8-2DA772E927FC),pointer_default(unique)interface IModuleSite:IUnknown /供部门组件改变当前显示模块,如点击了营销模块中的订单查找结果中的/办理人字段后自动跳转到人事模块中显示办理人的相关信息 HRESULT ChangeModule(in REFCLSID c

35、lsid,/模块的 CLSID/模块名字,仅用于提示 in,string WCHAR*pModuleName,/模块命令,指明欲让模块执行的命令,由模块解释 in ULONG command,in ULONG param);/模块命令的相关参数 HRESULT GetFrameWindow(out HWND*pHwnd);/返回主框架窗口;/基本框架实现 IErrorReport,其提供报告错误的功能 object,uuid(1A201ABA-A669-4ac7-9DF9-2DA772E927FC),pointer_default(unique)interface IErrorReport:I

36、Unknown /报告温和型错误,相当于警告/fileName 代表源代码文件的名字,row 代表错误所在行 HRESULT ReportSoftError(in,string WCHAR*fileName,in ULONG row,in,string WCHAR*errorString);/报告暴力型错误,相当于错误 HRESULT ReportHardError(in,string WCHAR*fileName,in ULONG row,in,string WCHAR*errorString);/基本框架实现 ICompanyInfo,其提供数据库服务器信息 object,uuid(1A2

37、01ABA-A669-4ac7-9DFA-2DA772E927FC),pointer_default(unique)interface ICompanyInfo:IUnknown /返回数据库服务器的相关信息,主机 IP、服务器名字及密码 HRESULT GetDataServerInfo(in,string WCHAR*loaction,in,string WCHAR*server,in,string WCHAR*password);/基本框架实现 ITaskManager,其提供任务的操作 interface ITask;object,uuid(1A201ABA-A669-4ac7-9DF

38、B-2DA772E927FC),pointer_default(unique)interface ITaskManager:IUnknown /添加任务 HRESULT AddTask(in,string WCHAR*taskString,/任务说明字符串 in ITask*pTask,/任务的指针/返回标识一个任务的 cookie out DWORD*pCookie);/基本框架实现 ITaskNotify,其提供任务的通知 object,uuid(1A201ABA-A669-4ac7-9DFC-2DA772E927FC),pointer_default(unique)interface I

39、TaskNotify:IUnknown /通知指定任务的进度已经变化 HRESULT ProcessRateChange(in DWORD cookie);/通知任务已经结束 HRESULT TaskOver(in DWORD cookie);/部门组件必须实现 IModule,其提供模块的操作 object,uuid(1A201ABA-A669-4ac7-9DFD-2DA772E927FC),pointer_default(unique)interface IModule:IUnknown /初始化模块,nID 为模块窗口的子窗口 ID HRESULT InitialModule(in IM

40、oduleSite*pSite,in UINT nID);/返回模块的图标 HRESULT GetIcon(out HICON*pHicon);/返回模块的名字 HRESULT GetName(out,string WCHAR*pName);/部门组件不一定实现 IModuleCommand,其提供执行模块所特有的命令 object,uuid(1A201ABA-A669-4ac7-9DFE-2DA772E927FC),pointer_default(unique)interface IModuleCommand:IUnknown HRESULT DoCommand(in ULONG comma

41、nd,in DWORD param);/部门组件不一定实现 IModuleNotify,其对模块提供一个通知途径 object,uuid(1A201ABA-A669-4ac7-9DFF-2DA772E927FC),pointer_default(unique)interface IModuleNotify:IUnknown HRESULT OnActivate();/模块切换时被激活 HRESULT OnDeActivate();/模块切换时取消激活;/部门组件必须实现 IModuleUI,其提供模块界面的相关操作 object,uuid(1A201ABA-A669-4ac7-9E00-2DA

42、772E927FC),pointer_default(unique)interface IModuleUI:IUnknown /返回模块的主要窗口 HRESULT GetMainWindow(out HWND*pHwnd);/翻译快捷键 HRESULT TranslateAccelerator(in MSG*pMsg);/部门组件不一定实现 IMenuUdpate,其提供模块界面中菜单的相关操作 object,uuid(1A201ABA-A669-4ac7-9E01-2DA772E927FC),pointer_default(unique)interface IMenuUpdate:IUnkn

43、own HRESULT GetMenu(out HMENU*pHmenu);HRESULT GetMenuItemString(in ULONG nID,out,string WCHAR*pString);/当部门组件创建了一个任务时,任务对象必须实现 ITask 以进行相应的任务管理 object,uuid(1A201ABA-A669-4ac7-9E02-2DA772E927FC),pointer_default(unique)interface ITask:IUnknown /返回任务的进度 HRESULT GetProcessRateOfTask(out float*pRate);HRE

44、SULT TerminateTask();/终止任务/将任务和任务管理器绑定起来 HRESULT SetTaskSite(in ITaskManager*pManager,in DWORD cookie);uuid(1A201ABA-A669-4ac7-9D00-2DA772E927FC),version(1.0),helpstring(ExampleBase 1.0 TypeLib)library ExampleBaseLib importlib(stdole32.tlb);importlib(stdole2.tlb);interface IModuleSite;interface IErr

45、orReport;interface ICompanyInfo;interface ITaskManager;interface IModule;interface IModuleCommand;interface IModuleNotify;interface IModuleUI;interface IMenuUpdate;interface ITask;interface ITaskNotify;上面的设计有个很明显的问题就是并没有体现组件的特性,只是很简单的部门组件和基本框架的组合,部门组件不能再有什么其他作为,是一种变相的 DLL 技术。这是样例的目标及特点(各部门完全不一样的信息处理

46、方式)决定的,就是一个插件接口。基本框架相当于一个播放器,而部门组件相当于一种音效处理插件。由于这只是个简单的例子,无法表现出 COM 组件特性的优点,但就此样例给出线程模型的例子已经是足够了。如果每个部门组件都只是信息录入、信息查询和信息管理(忽略其业务流程,如订单需要和出货联系起来),则可以使用另一种功能分割方式,即信息表现的接口、录入信息的接口、查询信息的接口和管理信息的接口(甚至还可以抽象出业务进而形成业务接口),这种方案将体现出组件的概念,但复杂程度亦增加了不少,因为其灵活性大大高于前一种方案。由于添加工具条的支持需要更多的代码,并且对本样例没有什么意义,故本样例中没有提供对工具条的

47、支持。作为一种习惯,将工程中所有的接口定义在一个.idl 文件中,然后再专门定一个项目生成其代理/占位组件,并导出 IID 等这类全局变量以供以后的使用,并且可以将类型信息一起加入其中,以减少最终完成中的文件数量。在前面的文章中我们介绍了 COM 接口及其与 COM 组件的关系,在这一节中我将向大家介绍 COM 组件的可重用性。四、包容与聚合 与所有面向对象的系统一样,COM 组件的可重用性是其很重要的一个特性。与 C+类在原代码级别的重用不同,COM 组件的重用是建立在对二进制代码重用的基础上的。具体包括包容(containment)和聚合(aggregation)两种重用模型。这两种重用机

48、制非常相似,其本质也都是在一个组件中对另外一个组件的使用。在包容机制中,外部组件除了实现自己的接口外,还包含了指向内部组件所有接口的指针,使内部组件接口相对于外部组件的客户是不可见的,只有通过外部组件提供的接口才能间接完成对内部组件接口的调用,并以此实现对已有组件的重用。由于包容机制为内部组件接口提供了外部接口实现,因此可以通过在外部接口添加适当的代码以完成与被重用组件所提供服务类似的功能。这有些类似于对 C+类虚函数的重载。图 6 包容与聚合重用模型 聚合机制的本质其实就是包容,只不过是其一个特例而已。采用聚合机制的组件并没有实现用于转发给内部组件接口的接口,而是直接将客户发出的对内部组件接

49、口的请求直接传递给内部组件的接口,使其直接暴露于外部组件的客户。但是客户在请求到此接口指针并对其接口进行调用时,仍不会意识到被重用组件的存在。由于外部组件对内部组件的重用只是通过传递对接口的请求而将被请求接口暴露于客户,因此只能实现与被重用组件所提供服务完全一样的重用功能。与包容不同,并不是所有的组件都能够支持聚合。至于在重用时是采取包容机制还是聚合机制,关键在于要实现的功能与待重用的组件所提供服务是类似还是完全一致。客户端使用 MFC 实现,其中的框架类 CMainFrame 实现了 IModuleSite、IErrorReport和 ICompanyInfo,而另一个窗口包装类 CTask

50、Manager 实现 ITaskManager,并由 CMainFrame聚合它以表现出 CMainFrame 实现了 ITaskManager。由于代码较长,本篇只罗列CMainFrame:OnCreate 和 CMainFrame 的定义,其中实现了获取部门组件的实例(通过 COM的组件类别功能进行记录,而非通过注册表),并进行管理。CMainFrame 的定义:#include NewStatusBar.h class CMainFrame:public CFrameWnd /MFC 定义宏 DECLARE_DYNCREATE(CMainFrame)DECLARE_MESSAGE_MAP

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 技术资料 > 其他杂项

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com