COM是由Microsoft提出的组件标准,它不仅定义了组件程序之间交互的标准,而且还提供了组件程序运行所需要的环境。COM体现了组件化程序设计的思想,复杂的应用程序被设计成一些小的、功能单一的组件模型,这些组件模块可以在同一台计算机或者不同的计算机上运行。COM是一门非常专业、系统和全面的知识,它涉及到很多的知识点,本章将只对COM进行入门介绍,同时帮助读者掌握在Windows Embedded Compact 7环境下使用ATL编写COM组件的方法,从而加深对COM技术的初步认识。

13.1 COM基本知识概述

13.1.1 什么是COM

COM是面向对象的软件模型,COM对象的概念有些类似于C++中对象的概念。在COM规范中,没有对COM对象的严格定义,COM组件提供给客户的是以对象形式封装起来的实体,客户与组件交互的实体是COM对象。

COM对象有自己的属性和方法,但这些属性和方法都被COM封装了起来,客户只有通过接口才能对COM的方法进行调用,而接口是COM与外界通信和交互的唯一途径。

13.1.2 什么是接口

接口是一组逻辑上相关的函数集合,它可以看作是指向该组函数集合的指针。

接口定义:接口定义遵循MIDL,它是Microsoft针对0SF的DCE(Distributes Computing Environment ) 规范中的IDL语言扩展。接口就是包含了一组函数的数据结构,它只负责定义函数。在对象创建后,接口就包含了接口指针和一个虚函数表,接口指针指向虚函数表(该函数表就成了一组函数指针的集合),每个函数指针指向‘'COM对象实现”里相应函数的实现。接口规范并不建立在任何编程语言基础上,而任何具有足够数据表达能力的语言,都可以对接口进行描述。  

COM中的所有接口都必须继承自IUnknown接口,IUnknown接口定义了3个基本方法COM对象实现:用于实现COM对象中所有接口中的所有方法,分别为AddRef,ReleascRef和Querylnterface,这些方法用于实现COM对象引用计数和接口查询等功能。

 

 

13-接口与COM关系图

13.1.3 COM基本结构

根据COM规范所建立的应用都是基于Client/Server模型的。一个完整的COM应用包括以下几个部分:COM服务器、服务器方COM库、客户方COM库和客户程序。下面将分别对各个部分做简要说明。

COM服务器:它是个容器,用来装载所有的COM对象。服务器方COM库:可以看作是容器的管理者,负责从容器中找出相应的COM对象,创建对象的实例。

客户方COM库:连接员,负责把客户的请求传送到服务方,负责客户COM的控制管理。

客户程序:COM服务的享受者。

一次完整的COM方法调用主要包括以下几个步骤:

(1)客户程序通知COM库,向COM库指出要调用的COM对象的GUID或lID。

(2)客户端COM库接受客户要求,向服务器端发送该要求,同时在客户进程内建立该COM对象的代理DLL(以后客户就同该代理DLL交互)。

(3)服务器COM库接受请求,从COM库中找出COM对象,建立该COM对象的实例(组件进程),在建立实例时,还会在实例的进程里创建一个存根dll。组件程序将通过该存根dll与客户端的存根dll进行交互。

(4)客户程序调用代理dll接口方法。

(5)代理dU把请求接口、方法、参数、数据以及打包列集(marshalling)发送给存根dll。

(6)存根dll接收来自代理的数据包和散集(Unmarshalling),并发送给组件程序。

(7)组件程序处理数据,返回给存根dll。

(8)存根dll列集(marshalling),发送给代理DLL。

(9)代理DLL返回结果给客户程序。

13.2 使用ATL创建COM示例

 13.2.1  ATLCOM支持概述

ATL是活动模板库(Active Template Library),也是一套基于c++的模板库。使用ATL能够快速地开发出高效、简洁的代码,同时为COM组件的开发提供了最大限度的代码自动化生成功能以及可视化支持。由于ATL的内容非常多,因此本书只对ATL进行一个入门式的介绍。如果读者想更全面、深入地了解ATL知识,可以查阅专门的ATL和COM书籍。

ATL实现COM接口的方式与MFC实现COM接口的方式有所不同,MFC是通过嵌套类来实现COM接口的,ATL则是使用多重继承的方式实现COM接口的。多重继承的方式似乎比嵌套类的方式更清晰也更易使用。下面就来介绍使用ATL实现COM接口的方法。为了方便说明,请先看下面的例子:

    class ATL_NO_VTABLE_CSimple

    public CComObjectRootEx<ccomMultiThreadModel>

    publiC CComCOClass<CSimple&CLSID_Simple>

    public IDispatchImpl<ISimple,  &IID_ISimple  &LIBID_CECOMSERVERLib>

    {

    public

    CSimple()

    {

    }

DECLARE_REGISTRY_RESOURCEID(IDR_SIMPLE)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM(CSimple)

  COM-INTERFACE-ENTRY(ISimple)

  COM_INTERFACE_-ENTRY(IDispatch)

END_COM_MAP()

    //ISimple

    public

    STDMETHOD(_COM_issue_errorex)(HRESULT_hr1,Iunknown  *pthisl , const  GUID

refiidl)

STDMETHOD(ShowSvrMsg) ();

};

  以上代码将实现ISimple接口的CSimple类定义。下面就对其中重要的代码进行解释说明

  .CComOhjectRootEx类和CComCoOass类;

  CComObjccIRootE.模板娄支持对象聚合或不聚合情形,同时它还实现了标准COM接口

的引用计数和接口查询方法;CComObjectRootEx模板类的构造函数需要传递COM对像的线程模型类。在Windows Embedded Compact 7中,只支持多线程模型,即CComMuhiThreadModel类。

    CComCoClass模板类为对象定义了缺省的类厂,同时它还支持聚台模型。CComCoCtass

模板类的定义如下:

    template.< class T,

    const  CLSID'*pclsid=&CLSID_NULL >

    class ccomCoClass

    参数T表示COM对象类,参数pclsid表示COM对像类的标识。

    .  iDispatchlmpl模板类。

    为了在脚本语言环境中使用COM组件.COM规范规定:需要在脚本语言环境中使用的COM必须实现iDispatch接口,此时的COM接口被称为派发接口。在创建COM时需要选择接口形式:custom或者dual,前者是自定义接口.也就是从IUnknown派生的接口:后者是从IDispatch派生的接口,当然IDispatch也必须从IUnknown派生。只有当接口是从IDispatch派生时,此COM组件才可以在不支持指针及脚本语言环境下使用。CSimple类继承于IDispatchImpl类,表示ISampic接口为双接口。

  .COM映射表宏。

  以上示例代码的黑体部分为COM映射表宏,它完成了接口查找的功能(Querylnterface)。用户想暴露多少个接口,就业写多少个COM_INTERFACE_ENTRY,ATL会自动用这些声明生成一个名为_ATL_INTMAP_FNTRY的接口表,然后在CComObjectRootBase类中提供个以这张接口表作为参数的IntemaIQuerylmerface函数,作出正确的Querylnteface.

    因此COM映射表宏可以理解如下:完成了COM接口查找功能,而不需要再去实现Querylnterface函数。

13.2.2  ATL创建COM对象示例

在这个例子里,将使用ATL创建一个简单的COM。该COM提供ISimple接口,并通过ISimple接口的ShowSvrMs9方法弹出一个消息提示框。下面就分步骤介绍使用ATL创建COM对象的过程。    

 (1)使用VS2008新建一个智能设备IATL智能设备项目CEComServer单击“确定”按钮后,进入如图所示的下一步向导。在该界面中,服务器类型选择“动态链接库(DLL)”,附加选项选择“支持MFC”复选框。然后单击“完成”按钮,便建立了一个ATL COM工程。最后将编译环境设置为yinchengOS创建模板如图13-2,定义编译环境如图13-3,工程设置如图13-4,定义支持如图13-5

 

图13-2程序运行效果图

 

图13-3定义编译环境

 

图13-4程序设置

 

图13-5定义支持项

(2)添加Com对象

在VS2008主菜单,项目|添加类,选择添加ATL简单对象,名称页主要输入两类信息,分别是C++信息与COM信息。名称设置为sample.如图13-6,13-7,13-8

 

图13-6选择类模板

 

图13-7类名称设置

 

图13-8设置类选项并创建之

在属性页中来设置COM对象底层特征,这里主要包括以下5种属性:

属性l——线程模型:指定新类的实例将在何种套间中运行。这里选择“自由”模型,也就是通过我们自身的线程同步来确保组件的访问安全。

属性2——接口类型:指定新类的接口类型。本页框提供两种可供选择的接El类型:Dual(双接口)和Custom(自定义接口)。双接口使用typeLib列集器并且从IDispatch接口派生,自定义接口需要自定义的代理/存根并且不是从IDispatch派生。

属性3——聚合设置:用于设置COM对象是否能够被聚合,即COM对象是否能够作为受控的内部对象参与聚合,此设置不影响COM对象是否能够作为外部控制对象参与聚合。

属性4——是否支持ISupportErrorlnf0接口。如果希望在COM对象发生错误时抛出COM异常,那么这个属性是必须要选中的。

属性5——是否支持连接点事件。如果选中此接口,那么向导将生成一个IconnectionPoint接口的实现,它可以向客户端发主动发生通知事件。

设置完以上属性后,单击“完成”按钮,便添加了Simple对象。

(3)为Simple对象添加接口方法。

选中ISimple接口,单击鼠标右键,在弹出的快捷菜单中选择“添加方法(M)”项,为Simple对象添加接口方法,如图13-9,13-10所示。

 

    图13-9Simple对象添加接口方法

在添加了ShowSvrMsg方法后,就要来具体实现ShowSvrMsg方法了。执行ShowSvrMsg方法将简单地弹出一个对话框,该方法的实现代码如程序

   

STDMETHODIMP CSimple::ShowSvrMsg(void)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

 

// TODO: 在此添加实现代码

::MessageBox(NULL,_T("此消息来自COM"),_T("COM测试"),MB_OK);

return S_OK;

}

    图13-10为接口添加方法向导

编译此工程并部署到模拟器后,VS2008会远程自动注册CEComServer。

13.2.3 创建客户端调用CEComServer

 (1)使用VS2008智能设备MFC智能设备应用程序向导创建一个基于对话框的应用程序CEComClient,编译环境设置为yinchengos

 (2)将上面创建的CEComServer工程生成的DLL添加到stdAfx.h文件中。可以在stdAfx.h文件末尾处添加如下语句:

 #import "C:\\CEComServer\\CEComServer\\yincheng_OS (x86)\\Debug\\CEComServer.dll" no_namespace

以上语句中的目录是CEComServer项目的编译目录,读者应该将该目录修改为自己的目录。

 (3)在对话框上放置一个按钮,用来调用ISample接口中的ShowSvrMsg方法。

       //调用COM  方法

void CCEComClientDlg::OnBnClickedBtnCall()

{

HRESULT hr; 

CLSID clsid;

ISimple *pSimple = NULL;

//初始化COM,对组件实例化

hr = CoInitializeEx(NULL,COINIT_MULTITHREADED);

//得到CLSID

hr = CLSIDFromProgID(OLESTR("CEComServer.Simple.1"),&clsid); 

if(FAILED(hr)) 

{

AfxMessageBox(L"未找到ID");  

goto error;

}

//得到ISimple COM接口

CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,__uuidof(ISimple),(void**)&pSimple);  

if(pSimple == NULL)    

{

AfxMessageBox(L"接口指针失败"); 

goto error ;

}

 

//调用ISimple方法

 pSimple->ShowSvrMsg();

 

error:

 //释放ISimple接口

 if (pSimple != NULL)

 {

pSimple->Release();

pSimple = NULL;

 }

 

 //释放COM组件库

 CoUninitialize();

}

至此,客户端调用Windows CE Comserver示例就编写完成了。编译下载到模拟器中,运行效果如图13-11。

 

图13-11效果图

13.3 可连接点对象及示例

13.3.1 可连接点对象概述

在前面讲述的COM示例中,客户与COM组件之间的通讯过程是单向的,即客户主动使用组件提供的接口方法。在这种交互过程中,客户是主动的,而组件是被动的,组件通过自身暴露给客户的接口来监听客户的请求,一旦收到客户的请求便做出反应,这样的接口被称之为“入接口”(incoming interface)。对于一个全面的交互过程,这样的单向通讯往往不能满足实际的需要,组件对象也应主动和客户进行通信。因此与“入接口”相对应,对象也可以提供“出接口”(outgoing interface),对象通过这些出接口与客户进行通讯。

如果一个COM对象支持一个和多个出接口,那么称这样的对象为可连接对象(connectableob ject)。出接口包含一组成员函数,每个成员函数代表一个通知。例如当COM对象中的某个状态改变时,此时可以通过COM对象向客户程序发送一个通知。

“出接口”与“入接口”有本质的区别,“入接口”是由COM对象本身来实现的,而“出接口”则由客户程序来实现,客户程序实现这些接口,并把接口指针告诉对象,以后对象便利用此接口指针来与客户程序进行通信。在客户程序方,实现这些接口的对象称之为“接收器”(sink),接收器本身也是一个COM对象,但它往往比较简单,负责处理组件的通知。

由此可见,客户与可连接对象之间的通讯是双向的,一方面,客户按照常规的途径调用

对象提供的接口,执行相应功能;另一方面,对象也可以通过它的出接口向客户发出请求、通知或事件。从对象的一方来看,它的入接口和出接口分别承担了这两个通讯过程:从客户的一方来看,前一个通讯过程由基本的客户代码完成,后一个通讯过程则是由客户方的接收器完成。因此整个通讯过程涉及到三个既独立又相关的部分:客户、对象和接收器。明白了这三个部分之问的关系,对可连接对象实现的理解就简单多了。下面将重点介绍这客户、对象和接收器三部分问的关系。

1.可连接对象的基本结构

可连接对象可以通过一个和多个出接口与客户端主动进行通讯。COM中约定可连接对象

必须实现一个lConnectionPointContainer接口,用于管理所有的出接口。每个出接口对应一个连接点对象,而连接点对象实现了IConnectionPoint接口。客户正是通过IConnectionPoint接口与可连接对象建立连接,此时客户端通过接收器与可连接对象进行通讯。

一个可连接对象可以包括多个连接点对象,也就是多个出接口,

可以通过IConnectionPointContainer接口来枚举此对象所支持的所有出接口。对于每一个出接口的连接点对象,也可以使用一个枚举器来管理它所连接的接收器。通过这两个枚举器,使得可连接对象支持多个出接口,且每个出接口支持与多个接收器连接。

2.客户程序与可连接对象的关系

以上介绍了可连接对象的基本结构后,下面接着介绍客户程序和可连接对象的关系。首先看一下“客户程序与可连接对象”的简单关系图。如图13-12,13-13

 

13-12可连接对象的基本结构

 

 

13-13  客户程序与可连接对象的简单关系图

客户程序把接收器的接口指针传递给可连接对象,可连接对象将记录下接收器的接口指针,以后在必要的时候,可以通过此接口指针调用接收器的成员函数。这里需要说明的是,接收器也是一个COM对象,但是它位于客户程序内部,并不需要通过COM库来创建,因此接收器并不需要CLSID来标识,接收器的标识和创建过程完全是客户程序内部的事情。对于客户程序外部而言,接收器也是一个单独的COM对象,它有自己的引用计数和接口查询方法。

客户与可连接对象建立连接的过程如下:

(1)调用pUnK—Advise(pSomeEventSet,&dwCookie),得到连接点容器接口。如果调用失败,则说明此对象不支持出接口。查找指定的连接点对象。

(2)调用pConnectionPoint—Advise(pSomeEventSet,&dwCookie),查找指定的链接对象。如果不再使用连接点容器,那么无论调用是否成功,都应该调用pConnection PointContainer- -Release方法,释放连接点容器接口。

(3)调用pConnectionPoint—Advise(pSomeEventSet,&dwCookie), 建立与接收器的连接客户端保存连接标识dwCookie,以便以后断开连接时使用

(4)当客户端要取消连接时,需要先调用pConnection PointoUnadvise(dwCookie)断开与接收器的连接,然后再调用pConnection Point--Release释放连接点对象。

对于可连接点对象就简要介绍到这里,下面将介绍一个连接点示例,希望通过该例子能加深读者对它的理解。

13.3.2 连接点示例

1.编写带连接点事件的cOM

 (1)使用VS2008新建一个智能设备IATL智能设备项目ConnectionCom,将编译环境设置为yinchengOS.

 (2)在类视图上单击鼠标右键创建一个ATL对象,名称为Add,将线程模型设置为“自由”,最后要确定选中“连接点”复选框,以支持连接点事件。添加完Add对象后,在类视图中,将出现Add和IAddEvents接口,后者是一个代理类,它将在客户端中被实现。它的出现是因为选中了“连接点”复选框。

 (3)为IAdd接口添加一个Add(LONG a,LONG b)方法。关于为一个接口添加方法的操作,在前面的章节中已经做了详细介绍,因此这里就不再赘述了。读者可以参考前面章节的说明进行接El方法的添加。为IAdd接口添加Add(LONG a,LONG b)方法的如图13-14

 

    图13-14IAdd接口添加一个Add(int aint b)方法

 (4)为IAddEvents接121添加ExecutionOver(LONG IResult)方法,如图13-7所示。该方法用来通知用户已经执行完IAdd接口中的Add方法。

备注:在类视图中,  IAddEvents接口在ConnectionComLib目录下。

 (5)编译项目,然后来实现连接点方法。具体操作如下:选中CAdd类,单击鼠标右键,在弹出快捷菜单中,选择“添加添加连接点”菜单项。此时,将打开下图所示的实现连接点对话框。如图13-15,13-16,13-17

 

13-15 为一IAddEvents接口添加ExecutionOver(LONG Lresult)方法

 

13-16实现连接点方法

 

13-17 添加到实现连接点列

在图13-10所示的对话框中,将源接口列表中的IAddEvents添加到右边的实现连接点列表中,单击“完成”按钮,便实现了连接点事件。此时CProxy类被生成,并.IAddComEvents带有一个Fire ExecutionOver(LONG IResult)方法。这个类将关注COM对象如何调用客户端接口。现在来实现原始IAdd接口中的Add方法。

    //IAdd接口的Add方法

    STDMETHODIMP Cadd::Add(LONG aLONG b)

    {

    AFX MANAGE STATE(AfxGetStaticModuleState())j

    Sleep(1000)

    //触发执行完毕命令

    Fire_ExecutionOver(a+b);

    return S_0K

    }

 (6)至此,带连接点事件的ATL COM组件就实现完毕了。读者最后可以编译本项目并部署到模拟器中,VS2008的部署工具将自动远程注册connectionCom.dll组件。

下面将继续编写一个客户端应用程序,以调用此COM。

2.编写客户端,调用带连接点事件的COM

 (1)使用VS2008智能设备IMFC智能设备应用程序向导创建一个基于对话框的应用程序ConnectionClient,编译环境设置为yincheng OS

 (2)设计对话框,界面如图l3-18所示。

 

    图13-18对话框界面

对话框上的主要控件及其属性设置如表13-1所示。

13-1                          对话框上的主要控件及其属性表

    lD

    属性

IDCEDTNUMBERI

输入框,用于输入数字l,对应成员变量m_numberl,类型long

 IDCEDLNUMBER2

输入框,用于输入数字2,对应成员变量mnumber2,类型long

 IDCBTNEXEC

 

按钮,标题设为“执行”。用于调用上面创建的ConnPointCom中的Add

方法,执行加法运算

 (3)新建Csink,继承于一laddE.entsCSink类用于实现jIAddEvems接口。由于CSink类是从IAddE,ents继承而来的,因此在头文件中必须包古iAddEvents的定义文件r所以应该引用ConnectionCom I程中的ConnectionComh文件a代码如下:

    *include”  \\ConnectionCom.h

接着在csInk中定义个私有变量ⅡLdwRefCount.用于存储COM对象的引用计数,代码如下:

  private,

    DWORD  m_dwRefCount;//访问计数变量

    然后在Csink构造函数巾,将m_dwRefCount初始化为0

    Csink::CSlnk()

{

m_dwRefCount =0;

}

 (4)ConnetionCom组件的相关GUID定义。打开Conne,tionCom工程中的Conne.tion Comi c文件,拷贝如下代码到Sink.cpp中,定义相关接口CUID

   

const IID IID_IAdd = {0x7C20780D,0x056A,0x4B4C,{0xA0,0xCB,0x0E,0x11,0x0F,0x5C,0x68,0xCF}};

const IID LIBID_ConnectionComLib = {0x6C01534B,0x653C,0x435B,{0x8A,0x8A,0xC2,0x6B,0xC7,0x7D,0xA6,0x5F}};

const IID DIID__IAddEvents = {0x6D98CC76,0xF53F,0x4DC3,{0xA0,0xF1,0x69,0x15,0x15,0x0B,0xEF,0xED}};

const CLSID CLSID_Add = {0x16753E3A,0x3279,0x4704,{0xA0,0x5B,0x44,0xDA,0xA9,0x4A,0x5C,0x9C}};

 (5)实现每一个被定义在_iAddEvents接口中的方法(注意一个COM接口是一个纯虚抽象类,继承它的类必须实现它所有的方法)。JAddEvernus .接口继承于IDispatch接口,共包括8个方法,其中的3个方法是每个COM对象都必须实现的3个方法(接口查询、引用计数加i3i用计数减1).其余4个是IDispath接口定义的方法,除此之外还有一个ExecutionnOver方法·它是_IAddEvens自定义的接口方法。这8个方法的定义如下

 //1、实现_IAddEvents接口的ExecutionOver方法

STDMETHODIMP ExecutionOver(LONG lResult);

 

//2、实现_IAddEvents接口的QueryInterface方法

HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void

**ppvObject);

 

//3、实现_IAddEvents接口的AddRef方法

ULONG STDMETHODCALLTYPE AddRef();

 

//4、实现_IAddEvents接口的Release方法

ULONG STDMETHODCALLTYPE Release();

 

//5,实现_IAddEvents接口的Invoke方法

HRESULT STDMETHODCALLTYPE Invoke( 

/* [in] */ DISPID dispIdMember,

/* [in] */ REFIID riid,

/* [in] */ LCID lcid,

/* [in] */ WORD wFlags,

/* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,

/* [out] */ VARIANT __RPC_FAR *pVarResult,

/* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,

/* [out] */ UINT __RPC_FAR *puArgErr);

 

//6,

HRESULT STDMETHODCALLTYPE GetTypeInfoCount( 

/* [out] */ UINT __RPC_FAR *pctinfo);

 

//7,

HRESULT STDMETHODCALLTYPE GetTypeInfo( 

/* [in] */ UINT iTInfo,

/* [in] */ LCID lcid,

/* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

 

//8,

HRESULT STDMETHODCALLTYPE GetIDsOfNames( 

/* [in] */ REFIID riid,

/* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,

/* [in] */ UINT cNames,

/* [in] */ LCID lcid,

/* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

8个函数的具体实现代码如下面程序清单所示。

//1、实现_IAddEvents接口的ExecutionOver方法

STDMETHODIMP CSink::ExecutionOver(LONG lResult)

{

CString strTemp;

strTemp.Format(L"结果是: %d", lResult);

AfxMessageBox(strTemp);

return S_OK;;

};

 

//2、实现_IAddEvents接口的QueryInterface方法

HRESULT STDMETHODCALLTYPE CSink::QueryInterface(REFIID iid, void

**ppvObject)

{

if (iid == DIID__IAddEvents)

{

m_dwRefCount++;

*ppvObject = (void *)this;

return S_OK;

}

if (iid == IID_IUnknown)

{

m_dwRefCount++;

*ppvObject = (void *)this;

return S_OK;

}

return E_NOINTERFACE;

}

 

//3、实现_IAddEvents接口的AddRef方法

ULONG STDMETHODCALLTYPE CSink::AddRef()

{

m_dwRefCount++;

return m_dwRefCount;

}

 

//4、实现_IAddEvents接口的Release方法

ULONG STDMETHODCALLTYPE CSink::Release()

{

ULONG l;

l  = m_dwRefCount--;

if ( 0 == m_dwRefCount)

delete this;

 

return l;

}

 

//5,实现Invoke方法

HRESULT STDMETHODCALLTYPE CSink::Invoke( 

/* [in] */ DISPID dispIdMember,

/* [in] */ REFIID riid,

/* [in] */ LCID lcid,

/* [in] */ WORD wFlags,

/* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,

/* [out] */ VARIANT __RPC_FAR *pVarResult,

/* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,

/* [out] */ UINT __RPC_FAR *puArgErr)

{

switch (dispIdMember)

{

//ExecutionOver方法

case 1:

{

LONG lResult = (pDispParams->rgvarg)->lVal;

ExecutionOver(lResult);

}

break;

default:

return DISP_E_BADINDEX;

}

}

 

 

HRESULT STDMETHODCALLTYPE CSink::GetTypeInfoCount( 

/* [out] */ UINT __RPC_FAR *pctinfo)

{

return S_OK;

}

 

HRESULT STDMETHODCALLTYPE CSink::GetTypeInfo( 

/* [in] */ UINT iTInfo,

/* [in] */ LCID lcid,

/* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo)

{

return S_OK;

}

 

HRESULT STDMETHODCALLTYPE CSink::GetIDsOfNames( 

/* [in] */ REFIID riid,

/* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,

/* [in] */ UINT cNames,

/* [in] */ LCID lcid,

/* [size_is][out] */ DISPID __RPC_FAR *rgDispId)

{

return S_OK;

}

(6)为对话框上的“执行”按钮添加单击事件代码。单击此按钮,将实现执行加法操作的功能,单击事件的实现代码如下程序清单所示。

   //执行按钮单击事件,调用IAddAdd方法

void CConnectionClientDlg::OnBnClickedBtnExec()

{

HRESULT  hr;

IUnknown *pSinkUnk;

CSink *pSink = NULL;

CComPtr<IAdd> pAdd;

//定义连接点容器指针

IConnectionPointContainer   * pCPC;

//定义连接点指针

    IConnectionPoint          * pCP;   

    DWORD         dwAdvise; 

 

UpdateData(TRUE);

//初始化COM,对组件实例化

hr = CoInitializeEx(NULL,COINIT_MULTITHREADED);

//得到IAdd COM接口

hr =pAdd.CoCreateInstance(CLSID_Add);

ASSERT(hr == S_OK);

 

    //判断IAdd接口是否有连接点事件,并得到连接点容器对象

    hr = pAdd->QueryInterface(IID_IConnectionPointContainer,(void **)&pCPC);

    ASSERT(SUCCEEDED(hr));

//得到连接点对象

    hr = pCPC->FindConnectionPoint(DIID__IAddEvents,&pCP);

ASSERT(SUCCEEDED(hr));

//CSink类创建一个连接点通知对象

    pSink = new CSink();

    ASSERT(pSink !=NULL);

//得到CSink类的接口指针

hr = pSink->QueryInterface (IID_IUnknown,(void **)&pSinkUnk);

//同连接点对象建立连接

hr = pCP->Advise(pSinkUnk,&dwAdvise); 

    //执行IAdd接口的Add方法

    pAdd->Add(m_number1 ,m_number2);

 

断开与连接点对象的连接

pCP->Unadvise(dwAdvise); 

pCP->Release();

//释放连接点容器对象

pCPC->Release();

//释放COM

CoUninitialize();

}

此外,还需要在ConnectionClientDlg.pp文件中引用CSink的定义文件。

至此连接点事件客户端测试程序就编写完成了。在运行客广端测试程序之前,应确保ConnectinCom对象已经被成功注册到模拟器上。在运行客户端程序时,分别输数字12和数字24,然后单击“执行”按钮,此时执行按钮便会调用COM对象IAddAdd方法来进行加法运算,运算完成后,便会主动通知客户端它的运算结果。如图13-19

 

13-19效果图