MFC中实现连接点(下)

来源:互联网 发布:格式化软件 编辑:程序博客网 时间:2024/06/06 02:33
 
  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文档中有一个示例.