`
yangyou230
  • 浏览: 1652621 次
文章分类
社区版块
存档分类

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
{
[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;
}
一切好了,我们下面将编写接受器对象。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics