COM 連接點讓物件能主動回呼客戶端。傳統手寫 IConnectionPointContainer、IConnectionPoint、Advise/Unadvise 代碼冗長且易錯。ATL 提供 AtlAdvise/AtlUnadvise 及 CComPtrBase::Advise 封裝,配合模板技術可將連接點管理壓縮至百行以內。
// 通用 Sink 實作基類
template <typename T, typename EventInterface, const GUID* evtLibID = NULL>
class ATL_NO_VTABLE CSinkImpT :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CSinkImpT<T, EventInterface, evtLibID>, &__uuidof(T)>,
public IDispatchImpl<EventInterface, &__uuidof(EventInterface), evtLibID>
{
public:
STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr)
{
T* pThis = static_cast<T*>(this);
return pThis->DoInvoke(dispidMember, riid, lcid, wFlags,
pdispparams, pvarResult, pexcepinfo, puArgErr);
}
// 衍生類別需實作 DoInvoke 處理具體事件
virtual HRESULT DoInvoke(...) { return E_NOTIMPL; }
};
// 連接點輔助類別:RAII 管理 Advise/Unadvise
template <typename EventInterface, typename EventProcessor>
class ConnectionPointHelper {
CComPtr<IUnknown> m_spInterface;
DWORD m_dwCookie = 0;
public:
ConnectionPointHelper(IUnknown* pInterface) : m_spInterface(pInterface) { Connect(); }
~ConnectionPointHelper() { Disconnect(); }
void Connect() {
HRESULT hr = E_FAIL;
do {
if (!m_spInterface || m_dwCookie) break;
CComObject<EventProcessor>* pTmp = nullptr;
hr = CComObject<EventProcessor>::CreateInstance(&pTmp);
if (FAILED(hr)) break;
CComQIPtr<IUnknown, &IID_IUnknown> spSink(pTmp);
hr = m_spInterface->Advise(spSink, __uuidof(EventInterface), &m_dwCookie);
} while (FALSE);
}
void Disconnect() {
if (m_dwCookie) {
AtlUnadvise(m_spInterface, __uuidof(EventInterface), m_dwCookie);
m_dwCookie = 0;
}
}
};
// 介面指標方式
ComDllLib::ITestComPtr pCom;
HRESULT hr = pCom.CreateInstance(L"Test.Com");
if (SUCCEEDED(hr)) {
CComPtr<IUnknown> spUnk;
hr = pCom->QueryInterface(IID_IUnknown, (void**)&spUnk);
if (SUCCEEDED(hr)) {
ConnectionPointHelper<ComDllLib::_ITestComEvent, CSink3> cph(spUnk);
LONG c = pCom->Add(1, 5); // 觸發事件回呼
}
}
// IDispatch 晚期繫結方式
CComPtr<IDispatch> spDisp;
hr = spDisp.CoCreateInstance(L"Test.Com");
if (SUCCEEDED(hr)) {
CComQIPtr<IUnknown> spUnk(spDisp);
ConnectionPointHelper<ComDllLib::_ITestComEvent, CSink3> cph(spUnk);
_variant_t ret, m=2, n=3;
spDisp.Invoke2(L"Add", &m, &n, &ret);
}
Advise,解構時 Unadvise,杜絕洩漏。IUnknown 與 IDispatch 兩種呼叫模式。所有需實作 COM 事件回呼的專案,特別是大量使用 ActiveX 元件、Office 自動化、或自訂 COM 伺服器的 C++ 應用程式。