連接點讓 COM 物件能「主動」回呼客戶端,實現雙向通訊。關鍵角色:
IConnectionPointContainer,管理多個輸出介面。IEventSink::EventHandle()),由客戶端實作。IConnectionPoint 的物件,維護連接清單(CONNECTDATA)。QueryInterface(IID_IConnectionPointContainer) 取得容器介面。FindConnectionPoint(IID_IEventSink, &pCP) 取得特定連接點。pCP->Advise(pSink, &dwCookie) 註冊接收器,取得連接識別碼 Cookie。pCP->GetConnections() 列舉連接,逐一呼叫 pSink->EventHandle() 觸發事件。pCP->Unadvise(dwCookie) + Release()。MFC 內建支援:
CCmdTarget 實作 IConnectionPointContainer + IEnumConnectionPoints。CConnectionPoint 實作 IConnectionPoint + IEnumConnections。BEGIN_CONNECTION_PART、CONNECTION_IID、DECLARE_CONNECTION_MAP 簡化宣告。// GUIDs.h
const IID CLSID_ConnObject = {0xEE888B01,...};
const IID IID_IEventServer = {0xEE888B02,...}; // 一般服務介面
const IID IID_IEventSink = {0xEE888B03,...}; // 輸出介面
// IConnObject.h
DECLARE_INTERFACE_(IEventServer, IUnknown) { STDMETHOD(DoSomething)() PURE; };
DECLARE_INTERFACE_(IEventSink, IUnknown) { STDMETHOD(EventHandle)() PURE; };
class CConnObject : public CCmdTarget {
DECLARE_DYNCREATE(CConnObject)
protected:
// 一般介面
BEGIN_INTERFACE_PART(EventServer, IEventServer)
STDMETHOD(DoSomething)();
END_INTERFACE_PART(EventServer)
// 連接點
BEGIN_CONNECTION_PART(CConnObject, SampleConnPoint)
CONNECTION_IID(IID_IEventSink)
END_CONNECTION_PART(SampleConnPoint)
DECLARE_INTERFACE_MAP()
DECLARE_CONNECTION_MAP()
DECLARE_OLECREATE(CConnObject)
void FireEvent(); // 觸發事件內部函式
};
IMPLEMENT_OLECREATE(CConnObject, "ConnObject", 0xEE888B01,...)
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()
CConnObject::CConnObject() { EnableConnections(); }
STDMETHODIMP CConnObject::XEventServer::DoSomething() {
METHOD_PROLOGUE(CConnObject, EventServer);
pThis->FireEvent();
return S_OK;
}
void CConnObject::FireEvent() {
const CPtrArray* pConns = m_xSampleConnPoint.GetConnections();
for (int i=0; iGetSize(); ++i) {
IEventSink* pSink = (IEventSink*)pConns->GetAt(i);
if (pSink) pSink->EventHandle();
}
}
class CClientDlg : public CDialog {
BEGIN_INTERFACE_PART(EventSink, IEventSink)
STDMETHOD(EventHandle)();
END_INTERFACE_PART(EventSink)
LPCONNECTIONPOINTCONTAINER m_pCPC = NULL;
LPCONNECTIONPOINT m_pCP = NULL;
DWORD m_dwCookie = 0;
LPUNKNOWN m_pUnk = NULL;
// ...
};
STDMETHODIMP CClientDlg::XEventSink::EventHandle() {
AfxMessageBox(_T("收到伺服器事件通知!"));
return S_OK;
}
// 連接按鈕
void CClientDlg::OnConnect() {
m_pUnk->QueryInterface(IID_IConnectionPointContainer, (void**)&m_pCPC);
m_pCPC->FindConnectionPoint(IID_IEventSink, &m_pCP);
IUnknown* pSinkUnk; m_xEventSink.QueryInterface(IID_IUnknown, (void**)&pSinkUnk);
m_pCP->Advise(pSinkUnk, &m_dwCookie);
}
// 觸發事件按鈕
void CClientDlg::OnEvent() {
IEventServer* pSrv; m_pUnk->QueryInterface(IID_IEventServer, (void**)&pSrv);
pSrv->DoSomething(); // 伺服器內部會呼叫 FireEvent → 回呼 EventHandle
}
IDispatch(自動化介面),支援腳本語言與 VB 客戶端,更靈活。IConnectionPointContainerImpl + IConnectionPointImpl),適合高效能元件。連接點機制是 COM 事件模型的基石,MFC 透過 CCmdTarget 與連接點巨集大幅降低實作複雜度。掌握「容器→連接點→Advise/Unadvise→FireEvent」流程,即可在元件中建立強大的事件驅動架構。