ATL7.1创建连接点组件

 
 
一 基础理论:
1) 源对象和接收器对象
接收器对象实现某个接口,源对象拥有该接口的指针,源对象可以调用该接口的方法。从而形成源对象以事件的方式通知接受器对象的效果。一个连接包含两部分,源对象和接收器对象。如图:
ISpeaker接口和_ISpeakerEvents接口对于各自的实现对象Demagogue和EarPolitic对象来说,只是个普通的接口。但是,由于Demagogue拥有了EarPolitic对象实现的_ISpeakerEvents接口的指针,使得_ISpeakerEvents变得特殊起来,Demagogue可以调用它的方法来通知EarPolitic对象,形成了“事件”。
2) 建立/断开连接
连接由EarPolitic对象建立,连接也由EarPolitic对象断开。
如果EarPolitic对象要建立连接,它需要通知Demagogue对象它已经实现了_ISpeakerEvents接口。如果要断开连接,它也需要通知Demagogue对象。如何通知呢:Demaogogue对象应该针对_ISpeakerEvents接口实现一个对应接口IConForSpeakerEvents,该接口要提供Advise和UnAdvise两个方法,这样EarPolitic对象通过调用这两个方法来通知Demagogue对象建立连接或者断开连接。
3)连接点
       IConForSpeakerEvents接口存在的目的只是为了让EarPolitic对象获得建立、断开对_ISpeakerEvents接口的方法。因此可以把连接事件接口的动作抽象出来,交由IConnectionPoint(连接点)来管理。
连接点允许接收器对象将事件接口的指针传送给源对象。接收器对象通过调用源对象的IConnectionPoint接口的方法Advise来建立连接。IConnectionPoint接口的方法Unadvise用来断开连接。(也可以运用AtlAdvise和AtlUnadvise函数来简化工作)。
IConnectionPoint : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE GetConnectionInterface(
            /* [out] */ IID *pIID) = 0;
       
        virtual HRESULT STDMETHODCALLTYPE GetConnectionPointContainer(
            /* [out] */ IConnectionPointContainer **ppCPC) = 0;
       
        virtual HRESULT STDMETHODCALLTYPE Advise(
            /* [in] */ IUnknown *pUnkSink,
            /* [out] */ DWORD *pdwCookie) = 0;
       
        virtual HRESULT STDMETHODCALLTYPE Unadvise(
            /* [in] */ DWORD dwCookie) = 0;
       
        virtual HRESULT STDMETHODCALLTYPE EnumConnections(
            /* [out] */ IEnumConnections **ppEnum) = 0;
       
    };
 
Advise方法我们可以看出EarPolitic对象的指针交给了连接点对象后,Demagogue对象会在合适的时候调用该接口的方法,形成“事件”。
4) 同时支持多个连接点
一个连接点对象对应一个事件接口,所以如果有不止一个事件接口,就应该有不止一个的连接点对象为其提供连接服务。
IConnectionPointContainer作为包容器,可以包容若干个IConnectionPoint对象。这就实现了同时支持多个连接点。
ATL中,IConnectionPointImpl类是用来实现IConnectionPoint对象的。ATL中,连接点映射表用来存放连接点的必要信息,该表中的每个表项存放连接点接口的GUID。比如:
 
IConnectionPointContainer接口负责管理多个IConnectionPoint接口。请看定义:
 IConnectionPointContainer : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE EnumConnectionPoints(
            /* [out] */ IEnumConnectionPoints **ppEnum) = 0;
       
        virtual HRESULT STDMETHODCALLTYPE FindConnectionPoint(
            /* [in] */ REFIID riid,
            /* [out] */ IConnectionPoint **ppCP) = 0;
       
    };
FindConnectionPoint方法可以很方便的获得容器内部管理的连接对象指针,而 IEnumConnectionPoints接口是为了方便枚举容器内部的每个连接对象接口的。
IEnumConnectionPoints : public IUnknown
    {
    public:
        virtual /* [local] */ HRESULT STDMETHODCALLTYPE Next(
            /* [in] */ ULONG cConnections,
            /* [length_is][size_is][out] */ LPCONNECTIONPOINT *ppCP, // typedef IConnectionPoint* LPCONNECTIONPOINT;
            /* [out] */ ULONG *pcFetched) = 0;
       
        virtual HRESULT STDMETHODCALLTYPE Skip(
            /* [in] */ ULONG cConnections) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE Reset( void) = 0;
       
        virtual HRESULT STDMETHODCALLTYPE Clone(
            /* [out] */ IEnumConnectionPoints **ppEnum) = 0;
    };
 
 
连接点容器管理了不止一个的连接点,那么这些连接点的信息是如何存储的呢?ATL中使用了连接点映射表来存放连接点管理的事件接口的IID.下面的宏
BEGIN_CONNECTION_POINT_MAP(CDemagogue)
       CONNECTION_POINT_ENTRY(DIID__ISpeakerEvents)
END_CONNECTION_POINT_MAP()
用来将DIID__ISpeakerEvents存放到连接点映射表中。
 
我们的组件类必须保存事件接口指针,如何保存呢,我们的类从CProxy_IEvent1Events<CMyClass>中派生。 CProxy_IEvent1Events类又从IConnectionPointImpl类派生。IConnectionPointImpl类里面有一个成员变量m_vec,这通常是一个CComDynamicUnkArray类的变量,CComDynamicUnkArray内部维护了IUnknown**数组
当EarPolitic对象调用IConnectionPointImpl::Advise方法建立连接时,实际上该事件接口指针被加入到m_vec变量中。因此当我们需要激发事件时,实际上就是找到m_vec中保存的对应的接口指针,并调用相应的方法。
CProxy_IEvent1Events将提供很多名称类似于 Fire_OnMethod的方法,这些辅助方法将帮助我们发出事件。
 
二 工程范例:
1)创建EventSource的ATL项目
2)添加组件类
注意由于COM+采用的事件方式与COM不同,所以,不要选择添加COM+1.0组件,而应该选择添加ATL简单对象。
支持连接点
 
向导生成后我们检查代码:
idl文件里面
 
import  "oaidl.idl";
import  "ocidl.idl";
 
[
     object,
     uuid(C74F7F62-D315-4BF6-9422-9B80D68DB4FA),
     dual,
     nonextensible,
     helpstring("ISample  接口"),
     pointer_default(unique)
]
interface  ISample : IDispatch{
};
[
     uuid(48D59498-7F95-4C2B-B3C3-B5DA3B407025),
     version(1.0),
     helpstring("EventSource 1.0  类型库")
]
library  EventSourceLib
{
     importlib("stdole2.tlb");
     [
         uuid(87A653F3-F08F-4C1F-B091-5F8FAA894E3C),
         helpstring("_ISample 事件接口")
     ]
     dispinterface _ISampleEvents
     {
         properties:
         methods:
     };
     [
         uuid(6CC7B493-5F8E-4C08-B66D-D9E5FD2342E0),
         helpstring("Sample Class")
     ]
     coclass Sample
     {
         [defaultinterface ISample;
         [default, source] dispinterface _ISampleEvents;
     };
};
红色的就是本组件首选的事件接口。ISampleEvents接口目前没有提供方法和属性。
 
类的声明如下:
class  ATL_NO_VTABLE CSample :
     public CComObjectRootEx<CComSingleThreadModel>,
     public CComCoClass<CSample, &CLSID_Sample>,
     public ISupportErrorInfo,
     public IConnectionPointContainerImpl<CSample>,
     public CProxy_ISampleEvents<CSample>,
public  IDispatchImpl<ISample, &IID_ISample, &LIBID_EventSourceLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
 
 
IConnectionPointContainerImpl和 CProxy_ISampleEvents<CSample>两个父类是关键。
 
BEGIN_COM_MAP(CSample)
     COM_INTERFACE_ENTRY(ISample)
     COM_INTERFACE_ENTRY(IDispatch)
     COM_INTERFACE_ENTRY(ISupportErrorInfo)
     COM_INTERFACE_ENTRY(IConnectionPointContainer)
END_COM_MAP()
 
COM MAP宏表明了对象支持 IConnectionPointContainer接口
 
BEGIN_CONNECTION_POINT_MAP(CSample)
     CONNECTION_POINT_ENTRY(__uuidof(_ISampleEvents))
END_CONNECTION_POINT_MAP()
 
上面这个宏将_ISampleEvents接口的 IID保存到连接点映射表中。
 
 
3)添加事件方法
我们现在想要在_ISampleEvents接口上添加方法 OnEvent1(BSTR Msg);当该事件被激发时,通过参数Msg将传递给接收对象一个BSTR。在未来的设计中,接收对象将显示这个Msg。我们将通过向导帮我们实现这个事件方法。
右键点击 _IsampleEvents接口,选择“添加方法“。填写下面的对话框。
完成后idl文件将作如下增加:
     dispinterface _ISampleEvents
     {
         properties:
         methods:
         [id(1), helpstring(" 方法OnEvent1")] HRESULT OnEvent1([in] BSTR Msg);
};
 
我们手工的在 CProxy_ISampleEvents类添加共有成员函数,该函数辅助激发事件
HRESULT Fire_OnEvent1(BSTR Msg)
        {
         CComVariant varResult;
         T* pT=static_cast<T*>(this);
         int nConIndex;
         CComVariant* pvars=new CComVariant[1];
         int nConnections=m_vec.GetSize();
         for(nConIndex=0;nConIndex<nConnections;nConIndex++)
         {
              CComPtr<IUnknown> sp=m_vec.GetAt(nConIndex);
              IDispatch* pDispatch=reinterpret_cast<IDispatch*>(sp.p);
              if(pDispatch!=NULL)
              {
                   VariantClear(&varResult);
                   pvars[0].vt=VT_BSTR;
                   pvars[0].bstrVal=SysAllocString(Msg);
                   DISPPARAMS disp={pvars,NULL,1,0};
                   pDispatch->Invoke(0x1,IID_NULL,LOCALE_USER_DEFAULT,DISPATCH_METHOD,&disp,&varResult,NULL,NULL);
              }
          }
          return varResult.scode;
   }
通过向导实现Fire_OnEvent1方法也很简单,右键点击 CSample,添加连接点,如图:
点击完成后,向导将生成Fire_OnEvent1的实现代码。
 
 
最后,我们添加一个ISample接口的方法Start,当该方法被调用时,他将激发_ISampleEvents接口的事件方法 OnEvent1。该方法的实现很简单
STDMETHODIMP CSample::Start(void)
{
     // TODO:  在此添加实现代码
     CComBSTR Msg(L"haha");
     Fire_OnEvent1(Msg);
     return S_OK;
}
 
一切好了,我们下面将编写接受器对象。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值