zoukankan      html  css  js  c++  java
  • C++ COM连接点事件event

       COM 中的典型方案是让客户端对象实例化服务器对象,然后调用这些对象。然而,没有一种特殊机制的话,这些服务器对象将很难转向并回调到客户端对象。COM 连接点便提供了这种特殊机制,实现了服务器和客户端之间的双向通信。使用连接点,服务器能够在服务器上发生某些事件时调用客户端。

    有了连接点,服务器可通过定义一个接口来指定它能够引发的事件。服务器上引发事件时,要采取操作的客户端会向服务器进行自行注册。随后,客户端会提供服务器所定义接口的实现。

    客户端可通过一些标准机制向服务器进行自行注册。COM 为此提供了 IConnectionPointContainer 和 IConnectionPoint 接口。

    COM 连接点服务器的客户端可用 C++ 和 C# 托管代码来编写。C++ 客户端会注册一个类的实例,该类提供了接收器接口的实现。托管客户端会注册单个事件的委托,因而会按每个事件通知方法创建单个接收器,具体参考C#的互操作部分内容。

    组件的连接点编写比较简单,关键是如何在客户端实现事件监听与接收。


    二、连接点客户端实现(VC)

    1、 包含“工程_i.h”头文件,引入“工程.tlb”ole库文件。比如:
    #include "ATLDemo_i.h"
    #import "ATLDemo.tlb" named_guids raw_interfaces_only
    2、 创建一个类:由_IXXXEvent派生过来。(XXX为实际事件名)
    实现类各个虚函数重载,如果_IXXXEvent是IUnkown接口只需要重载QueryInterface、AddRef、Release函数;如果_IXXXEvent是双向接口需要重载实现IUnkown接口三个函数和IDispatch接口四个函数。


    实现事件功能,通过函数、用SINK_ENTRY_INFO实现事件的映射、Invoke函数里面实现(通过事件ID)三种方法之一来实现。

    BEGIN_SINK_MAP(CMyObj)
    SINK_ENTRY(IDC_CIRCCTL1, DISPID_CLICK, OnClick_CircCtrl1)
    SINK_ENTRY(IDC_CIRCCTL2, DISPID_CLICK, OnClick_CircCtrl2)
    END_SINK_MAP()

    3、 如何调用
    3.1使用工程支持COM,使用afxoleinit或者CoInitialize/Un CoInitialize
    3.2得到组件接口
    3.3得到连接点容器,查找连接点。
    3.4利用Advise将一个监听对象传给组件,这样当事件发生的时候事件就会响应。在不使用时,通过UnAdvise来断开连接点事件。同时也利用AfxConnectionAdvice将监听对象传给组件接口。
    3.5 释放资源。

     1 class CSink : public _IXXXXXXEvents  
     2 {  
     3 private:
     4     DWORD       m_dwRefCount;
     5  
     6 public:  
     7     CSink() {m_dwRefCount = 0;}
     8     virtual ~CSink(void){}  
     9     STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) { return E_NOTIMPL; }  
    10     STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)   { return E_NOTIMPL; }  
    11     STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)  { return E_NOTIMPL; }  
    12  
    13     STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)  
    14     {  
    15         LONG *lValue = pVarResult->plVal;
    16         printf(" ---------the result %d
    ",*lValue);
    17         OnReceiver(lValue);
    18         return S_OK;  
    19     }  
    20     STDMETHOD(QueryInterface)(REFIID iid, LPVOID* ppv)
    21     { 
    22         if ((iid == __uuidof(_IFirstClassEvents)) || //(iid == __uuidof(IMarshal)) ||
    23             (iid == __uuidof(IDispatch)) ||
    24             (iid == __uuidof(IUnknown)))
    25         {
    26             m_dwRefCount++;
    27             *ppv = this;
    28         }
    29         else
    30         {
    31             *ppv = NULL;
    32             return E_NOINTERFACE;
    33         }
    34         return S_OK;
    35     }
    36     STDMETHOD_(ULONG,AddRef)() 
    37     {
    38         return InterlockedIncrement(&m_dwRefCount); 
    39     }
    40     STDMETHOD_(ULONG,Release)()
    41     { 
    42         InterlockedDecrement(&m_dwRefCount);
    43         if (m_dwRefCount != 0)
    44             return m_dwRefCount;
    45         delete this;
    46         return 0;
    47     }
    48     HRESULT STDMETHODCALLTYPE OnReceiver( LONG * ResponseID)
    49     {
    50         printf("C++ SINK: Addition started event fired ..%d. 
    ",ResponseID);
    51         return S_OK;
    52     };
    53  
    54 };
     1  CoInitialize(NULL );
     2  
     3     IUnknown*pUnk=NULL; 
     4     IFirstClass* pFirst=NULL;  
     5     IConnectionPoint *pEvent= NULL;
     6     int ierror;
     7     HRESULT hr; 
     8     IConnectionPointContainer* pContainer =NULL;
     9     ULONG dwAdvise;
    10     IUnknown *SinkUnk ;    
    11     long ireult;
    12     hr = CoCreateInstance(CLSID_FirstClass,NULL,CLSCTX_INPROC_SERVER,IID_IFirstClass,(void**)&pUnk);  
    13     hr = pUnk->QueryInterface(IID_IFirstClass,(void**)&pFirst);
    14  
    15     hr = pFirst->QueryInterface(IID_IConnectionPointContainer,(void**)&pContainer);
    16     hr = pContainer->FindConnectionPoint(DIID__IFirstClassEvents,&pEvent);
    17  
    18     CSink* sinkptr = new CSink();
    19     hr = sinkptr->QueryInterface(IID_IUnknown,(void**)&SinkUnk);
    20     if(!SUCCEEDED(hr)) 
    21         return 0; 
    22     hr = pEvent->Advise(SinkUnk,&dwAdvise);
    23     pFirst->Add(34,56,&ireult);
    24     hr = pEvent->Unadvise(dwAdvise);
    25     printf("result----%d
    ",ireult);
    26         pFirst->Release();
    27         pUnk->Release();
    28        CoUninitialize();

    使用ATL实现连接点


    接收事件的就必须实现一个专用的COM对象,这也是实现上最困难的地方。而且,很多连接点都要求通过IDispatch接口传递事件,这样就造成了更大的困难。
    我们按照步骤说明如何编写代码在普通的应用程序中接收通过IDispatch接口发送的事件。
    1.1 添加ATL头文件


    使用ATL前必须包含以下头文件:atlbase.h和atlcom.h,并且定义_Module变量[2]。把以下代码加到stdafx.h是最方便的:

    #include <atlbase.h>

    extern CComModule _Module;
    #include <atlcom.h>

    在cpp文件中要对_Module进行定义。如下:

    CcomModule _Module;

    在添加了这两行之后,我们就可以使用ATL的功能了。而不必创建ATL的项目。
    当然,如果项目是通过ATL项目向导产生的就不必再次添加头文件了。
    1.2 ATL初始化


    使用ATL之前必须初始化,使用完之后还要终止ATL。
    对于控制台程序,初始化ATL的方法是:

    int main(int argc, char * argv[], char ** env)
    {
    _Module.Init(NULL, (HINSTANCE)GetModuleHandle(NULL));
    ...

    如果是Windows程序或动态连接库,使用传入的Instance句柄初始化。

    int WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
    {
    _Module.Init(NULL, hInstance);
    ...

    终止ATL使用以下方法:

    _Module.Term();

    初始化ATL必须在任何ATL操作前进行,而终止则必须在所有ATL操作结束后进行。
    1.3 事件接收器


    1.3.1 引入COM对象


    为了方便,本例使用#import引入COM类,也可以通过其它方式引入COM对象,但其它代码要做相应修改。

    #import "[COM或TLB的路径]" named_guids, no_namespace

    注意,必须要添加named_guids属性。否则,#import就不会生成TLB类的GUID定义。
    1.3.2 事件接受器类


    A. 类定义


    事件接收器是一个类,从IDispEventImpl继承。IDispEventImpl是ATL中的一个模板,专用于接收IDispatch接口的事件。IDispEventImpl的定义是:

    class IDispEventImpl<nID, T, pdiid, plibid, MajorVer, MinorVer, tihClass>
    {
    };

    nID可以设成0,T是事件接收器类的名字,pdiid是事件接口的GUID,plibid是类型库GUID的指针,MajorVer和MinorVer分别是主、次版本号,tihClass是用于处理TLB的类。TihClass可以使用缺省值。
    事件接收器类的代码如下:

    class EventReceiver :
    public IDispEventImpl<0,
    EventReceiver,
    &DIID__ConnectPointInterface,
    &LIBID_xxxLib, 1, 0>
    {
    ...
    };

    B.事件响应函数


    对于每个要响应的事件,要在事件响应类中添加相应的事件响应函数。事件响应函数的名称可以自己选择,但参数和返回值必须和COM对象的定义一致。
    事件响应函数是标准的接口方法,应该使用STDMETHOD和STDMETHODIMP声明。
    本例中,我们接收一个事件,具有一个整形参数。首先,在事件响应类中添加函数定义:

    class EventReceiver : public ...
    {
    ...
    public:
    STDMETHOD(EventNotify)(int i);
    ...
    }

    然后添加具体的事件响应代码:

    STDMETHODIMP EventReceiver::EventNotify(int i)
    {
    printf("EventReceiver : %d ", i);
    return S_OK;
    }

    C.事件对应表


    事件响应类中要定义事件对应表,说明哪个函数响应哪个事件。事件对应表通过三个宏实现,它们是:BEGIN_SINK_MAP,SINK_ENTRY_EX,END_SINK_MAP。
    BEGIN_SINK_MAP有一个参数,是事件接收类的名称。SINK_ENTRY_EX有四个参数,分别是nID、diid、EventId和FuncName。nID是0、diid是事件接口的GUID、EventId是事件编号、FuncName是响应函数的名称。
    以下代码中的事件响应表声明事件编号是1的事件通过EventNotify函数响应。可以写多个SINK_ENTRY_EX来响应多个事件。

    class EventReceiver : public ...
    {
    ...
    public:
    BEGIN_SINK_MAP(EventReceiver)
    SINK_ENTRY_EX(0, DIID__IConnectionPointTestObjEvents, 1, EventNotify)
    END_SINK_MAP()
    ...
    }

    1.4 连接对象和关闭连接


    事件连接类实现完成之后,要连接到具体的COM对象才可以接收事件。IDispEventImpl通过DispEventAdvise方法连接到COM对象。DispEventAdvise使用要连接的COM对象指针作为参数。连接前要先生成事件接收类的实例,代码如下:

    EventReceiver * pReceiver = new EventReceiver;
    pReceiver->DispEventAdvise(pObj);

    如果不需要在接收事件,应该使用DispEventUnadvise函数关闭连接,代码如下:

    pReceiver->DispEventUnadvise(pObj);

  • 相关阅读:
    PE格式详细讲解2 系统篇02|解密系列
    结构体与共用体06 零基础入门学习C语言58
    PE格式详细讲解3 系统篇03|解密系列
    我的学习路
    结构体与共用体07 零基础入门学习C语言59
    PE格式详细讲解2 系统篇02|解密系列
    PE格式详细讲解3 系统篇03|解密系列
    Windows Azure 社区新闻综述(#61 版)
    Django 现可在 Windows Azure 上使用
    Windows Azure Active Directory正式发布:已提供了超过 2650 亿个身份验证和服务了290万个组织!
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/15137761.html
Copyright © 2011-2022 走看看