3.添加基类为CCmdTarget的类CConnObject.在类声明文件CConnObject1.h中加上#include “IConnObject.h”,在类声明中写入:
protected:
……
//声明实现IEventServer接口的嵌套类
BEGIN_INTERFACE_PART(EventServer,IEventServer)
STDMETHOD(DoSomething)();
END_INTERFACE_PART(EventServer)
DECLARE_INTERFACE_MAP()
//声明实现连接点的嵌套类
BEGIN_CONNECTION_PART(CConnObject,SampleConnPoint)
CONNECTION_IID(IID_IEventSink)
END_CONNECTION_PART(SampleConnPoint)
DECLARE_CONNECTION_MAP()
DECLARE_OLECREATE(CConnObject)
说明:BEGIN_CONNECTION_PART和END_CONNECTION_PART宏声明了实现连接点的嵌套类 SampleConnPoint,并且是基于CConnectionPoint类的,如果需要重载CConnectionPoint类的成员函数或者添加 自己的成员函数,可以在这两个宏中声明.这里,CONNECTION_IID宏重载了CConnectionPoint::GetIID()函数.使用 DECLARE_CONNECTION-MAP()宏声明连接点映射表.
4.在类CConnObject的实现文件中写入
IMPLEMENT_OLECREATE(CConnObject,"ConnObject",
0xee888b01, 0xea9c, 0x11d3, 0x97, 0xb5, 0x52, 0x54, 0xab, 0x19, 0x19, 0x30);
BEGIN_INTERFACE_MAP(CConnObject,CCmdTarget)
INTERFACE_PART(CConnObject,IID_IEventServer,EventServer)
INTERFACE_PART(CConnObject,IID_IConnectionPointContainer,ConnPtContainer)
END_INTERFACE_MAP()
BEGIN_CONNECTION_MAP(CConnObject,CCmdTarget)
CONNECTION_PART(CConnObject,IID_IEventSink,SampleConnPoint)
END_CONNECTION_MAP()
说明:A.必须在接口映射中写入INTERFACE_PART(CConnObject, IID_IConnectionPointContainer,ConnPtContainer)以实现 IConnectionPointContainer接口.注意,CCmdTarget类内嵌有才ConnPtContainer类以实现 IConnectionPointContainer接口,并用m_xConnPtContainer加以记录.
B.用BEGIN_CONNECTION_MAP和END_CONNECTION_MAP宏实现连接点映射.CONNECTION_PART定义了实现连接点的类.
5.在CConnObject::CConnObject()中写入:
EnableConnections();
6.实现IEventServer接口
IEventServer接口是基于IUnknown接口的,实现IUnknown接口的方法这里不在赘述.在实现文件中写入:
STDMETHODIMP
CConnObject::XEventServer::DoSomething()
{
//DoSomething
METHOD_PROLOGUE(CConnObject,EventServer)
pThis->FireEvent();
return S_OK;
}
DoSomething()方法可以为客户提供需要的 服务.这里着重的是可连接对象在此处触发客户事件接收器的事件,FireEvent()函数是ConnObject类实现的专门触发事件的的函数,代码如下:
void CConnObject::FireEvent()
{
//获取连接点上的连接指针队列
const CPtrArray* pConnections = m_xSampleConnPoint.GetConnections();
ASSERT(pConnections!=NULL);
int cConnections = pConnections->GetSize();
IEventSink* pIEventSink;
//对每一个连接触发事件
for(int i = 0; i < cConnections; i++)
{
//获取客户事件接收器接口指针
pIEventSink = (IEventSink*)(pConnections->GetAt(i));
ASSERT(pIEventSink!=NULL);
//调用客户事件接受器事件处理函数
//此函数是出接口定义,由客户事件接收器实现的
pIEventSink->EventHandle();
}
}
3、客户事件接收器(Sink)
事件接收器也是COM对象,也可以用嵌套类来实现,但是它只是客户的一个内部对象,所以可以没有CLSID和类厂.下面示例是一个对话框程 序,对话框有三个按钮:”连接”(IDC_CONNECT),”断开”(IDC_DISCONNECT),”事件”(IDC_EVENT).
1.创建一个基于对话框的工程:ConnClient.
2.在CConnClientDlg中首先加入#include “IConnObject.h”,然后在对话框类声明中声明事件接收器嵌套类:
BEGIN_INTERFACE_PART(EventSink,IEventSink)
STDMETHOD(EventHandle)();
END_INTERFACE_PART(EventSink)
同时声明几个私有变量:
private:
LPCONNECTIONPOINTCONTAINER pConnPtCont;//记录组件对象
//IConnectionPointContainer接口指针
LPCONNECTIONPOINT pConnPt;//记录连接点接口指针
DWORD m_dwCookie;//记录连接标识
IUnknown* m_pIUnknown;//用以记录组件对象IUnknown接口指针
3.实现事件接收器:
STDMETHODIMP_(ULONG)
CConnClientDlg::XEventSink::AddRef()
{
return 1;
}
STDMETHODIMP_(ULONG)
CConnClientDlg::XEventSink::Release()
{
return 0;
}
STDMETHODIMP
CConnClientDlg::XEventSink::QueryInterface(REFIID riid,void** ppvObj)
{
METHOD_PROLOGUE(CConnClientDlg,EventSink)
if(IsEqualIID(riid,IID_IUnknown)||
IsEqualIID(riid,IID_IEventSink))
{
*ppvObj = this;
AddRef();
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
STDMETHODIMP
CConnClientDlg::XEventSink::EventHandle() //此函数将被可连接对象调用
{
::AfxMessageBox("源对象向事件接收器发出了的通知!");
return S_OK;
}
4.初始化COM库并创建组件对象实例
在CConnClientDlg::OninitDialog()中写入:
HRESULT hResult;
hResult = ::CoInitialize(NULL);
if(FAILED(hResult))
{
::AfxMessageBox("不能初始化COM库!");
return FALSE;
}
m_pIUnknown = NULL;
hResult = ::CoCreateInstance(CLSID_ConnObject,NULL,
CLSCTX_INPROC_SERVER,IID_IUnknown,(void**)&m_pIUnknown);
if(FAILED(hResult))
{
m_pIUnknown = NULL;
::AfxMessageBox("不能创建ConnObject对象!");
return FALSE;
}
m_dwCookie = 0;//预置连接标识为0
5.在按钮”连接”(IDC_CONNECT)的CLICK事件处理函数void CConnClientDlg::OnConnect()中写入:
void CConnClientDlg::OnConnect()
{
if(m_dwCookie!=0)
{
return;
}
if(m_pIUnknown!=NULL)
{
HRESULT hResult;
hResult = m_pIUnknown->QueryInterface(IID_IConnectionPointContainer,
(void**)&pConnPtCont);
if(FAILED(hResult))
{
::AfxMessageBox("不能获取对象的IConnectionPointContainer接口!");
return;
}
ASSERT(pConnPtCont!=NULL);
hResult = pConnPtCont->FindConnectionPoint(IID_IEventSink,&pConnPt);
if(FAILED(hResult))
{
pConnPtCont->Release();
::AfxMessageBox("不能获取对象的IEventSink连接点接口!");
return;
}
ASSERT(pConnPt!=NULL);
//获取事件接收器指针
IUnknown* pIEventSink;
m_xEventSink.QueryInterface(IID_IUnknown,(void**)&pIEventSink);
//通过连接点接口的Advise方法将事件接收器指针传给可连接对象
if(SUCCEEDED(pConnPt->Advise(pIEventSink,&m_dwCookie)))
{
::AfxMessageBox("与可连接对象ConnObject建立连接成功!");
}
else
{
::AfxMessageBox("不能与ConnObject建立连接!");
}
pConnPt->Release();
pConnPtCont->Release();
return;
}
}
上述代码与可连接对象的连接点建立连接.
6.编写按钮”断开”(IDC_DISCONNECT)的CLICK处理函数如下:
void CConnClientDlg::OnDisconnect()
{
if(m_dwCookie==0)
{
return;
}
pConnPt->Unadvise(m_dwCookie);
pConnPt->Release();
pConnPtCont->Release();
m_dwCookie = 0;
}
7.编写按钮”事件”(IDC_EVENT)的CLICK处理函数:
void CConnClientDlg::OnEvent()
{
if(m_pIUnknown!=NULL)
{
IEventServer* pIEventServer;
HRESULT hResult;
hResult = m_pIUnknown->QueryInterface(IID_IEventServer,(void**)&pIEventServer);
if(FAILED(hResult))
{
::AfxMessageBox("不能获取IEventServer接口!");
return;
}
pIEventServer->DoSomething();
}
}
这里,客户调用组件提供的 服务DoSomething(),而正如前面所看到的,组件对象将在这个函数中触发一个由客户事件接收器处理(CConnClientDlg::XEventSink::EventHandle())的事件.
8.在退出应用时:
void CConnClientDlg::OnCancel()
{
m_pIUnknown->Release();
::CoUninitialize();
CDialog::OnCancel();
}
运行程序后,首先点击”连接”,然后点击”事件”按钮,这时将弹出MessageBox,并提示” 源对象向事件接收器发出了的通知!”.
小结
正是由于有了可连接对象这一机制,实现了客户与组件对象的双向通信,使组件对象具有了事件机制.这种类似于” 服务器推送(Server push)”的 技术在分布式应用系统中十分重要.
本文所举示例是用基于IUnknown接口实现的,其实,用 自动化接口IDispatch作为出接口更为方便.需要说明的
是,用ATL来写可连接对象更为简洁,MSDN文档中有一个示例.