接收器对象实现某个接口,源对象拥有该接口的指针,源对象可以调用该接口的方法。从而形成源对象以事件的方式通知接受器对象的效果。一个连接包含两部分,源对象和接收器对象。如图:
ISpeaker接口和_ISpeakerEvents接口对于各自的实现对象Demagogue和EarPolitic对象来说,只是个普通的接口。但是,由于Demagogue拥有了EarPolitic对象实现的_ISpeakerEvents接口的指针,使得_ISpeakerEvents变得特殊起来,Demagogue可以调用它的方法来通知EarPolitic对象,形成了“事件”。
连接由EarPolitic对象建立,连接也由EarPolitic对象断开。
如果EarPolitic对象要建立连接,它需要通知Demagogue对象它已经实现了_ISpeakerEvents接口。如果要断开连接,它也需要通知Demagogue对象。如何通知呢:Demaogogue对象应该针对_ISpeakerEvents接口实现一个对应接口IConForSpeakerEvents,该接口要提供Advise和UnAdvise两个方法,这样EarPolitic对象通过调用这两个方法来通知Demagogue对象建立连接或者断开连接。
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对象会在合适的时候调用该接口的方法,形成“事件”。
一个连接点对象对应一个事件接口,所以如果有不止一个事件接口,就应该有不止一个的连接点对象为其提供连接服务。
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的方法,这些辅助方法将帮助我们发出事件。
注意由于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保存到连接点映射表中。
我们现在想要在_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;
}
一切好了,我们下面将编写接受器对象。
分享到:
相关推荐
这当然可以理解为:ATL 7.1和MFC4.2也可以免费的用于商业开发了!!因为该驱动包是可以用于商业开发,免费提供下载的。 我已经从中提取了ATL 7.1供大家下载使用:http://download.csdn.net/source/1672909 这是...
ATL创建进程外COM组件服务,包含COM组建客户端测试程序,使用时,需要先通过批处理文件将COM组建服务注册到Windows系统中(win10测试通过),具体可参考我的文章《ATL创建进程外COM组件服务(C++图解说明)》
从Windows Driver Kit Version 7.1.0中分离出的ATL7.1 用处就不说了
初学COM,使用ATL创建COM组件,并测试COM组件的小例子
如何用VS2005 ATL 创建 COM 组件
创建Alt的DLL时,注意不要选 安全生命开发周期SDL,否则添加ATL类时会失败,此例子根据https://blog.csdn.net/zj510/article/details/39056097来,另外添加新接口时,如果不熟悉,最好使用向导,否则自己要在多处...
本程序使用的是ATL-COM支持连接点的模式。其中反映了Observer的支持。使用多线程调用Fire函数。
如何用ATL建立复合控件,以及如何响应连接点事件。考虑到代码很简单,程序中没有加注释,如有不清楚请参看MSDN,代码仓促中完成,不免有疏漏,见谅
ATL COM组建的创建和调用示例, 采用VS进行开发,可参考
使用ATL开发COM组件文档
这是一个演示ATL连接点的一个例子,包括c++客户端和js客户端
使用MFC创建COM程序实例,使用ATL创建COM程序实例
本代码借鉴了网上教程结合自己的经验,对ATL COM连接点进行了简单测试,
mfc visual c++ Atl开发集合组件实例
这篇文章首先介绍了如何创建一个ATL工程,并为其添加接口类和实现接口方法;然后介绍了如何利用自动化的方式调用由ATL生成的dll,即在程序中获取CLSID和IID然后创建接口实例;最后分析了在程序中CLSID和IID获取时候...
用ATL创建ActiveX控件,电子文档,相互学习
ATL com 组件开发完整实例,需要的可做参考。ATL,Active Template Library活动(动态)模板库,是一种微软程序库,支持利用C++语言编写ASP代码以及其它ActiveX程序。通过活动模板库,可以建立COM组件,然后通过ASP...
ATL&WTL学习资料(含WTL7.1EXE) 从ATL到WTL 进行讲解 主要是关于界面UI那块的
介绍在VC6.0下如何创建ATL com组件,并提供实例说明如何使用以及释放ATL控件。 另外,ATL组件也可以应用在网页中。
使用ATL完成可连接对象和对应接收器的实现,包含完整的代码演示,可以对照学习。