ATL7.1创建连接点组件

来源:互联网 发布:半球面车削编程 编辑:程序博客网 时间:2024/04/26 11:34
 
目录:
ATL7.1创建连接点组件... 1
目录:... 1
基础理论:... 1
1) 源对象和接收器对象... 1
2) 建立/断开连接... 1
3)连接点... 2
4) 同时支持多个连接点... 2
连接点容器:... 2
连接点映射表... 3
激发事件... 3
工程范例:... 3
1)创建EventSourceATL项目... 3
2)添加组件类... 4
3)添加事件方法... 6
 
 
基础理论:
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
     {
         [default] interface 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;
}
 
一切好了,我们下面将编写接受器对象。
 
 
原创粉丝点击