基于VC編寫COM連接點(diǎn)事件的分析介紹
COM 中的典型方案是讓客戶端對象實(shí)例化服務(wù)器對象,然后調(diào)用這些對象。然而,沒有一種特殊機(jī)制的話,這些服務(wù)器對象將很難轉(zhuǎn)向并回調(diào)到客戶端對象。COM 連接點(diǎn)便提供了這種特殊機(jī)制,實(shí)現(xiàn)了服務(wù)器和客戶端之間的雙向通信。使用連接點(diǎn),服務(wù)器能夠在服務(wù)器上發(fā)生某些事件時調(diào)用客戶端。
原理如下圖:


有了連接點(diǎn),服務(wù)器可通過定義一個接口來指定它能夠引發(fā)的事件。服務(wù)器上引發(fā)事件時,要采取操作的客戶端會向服務(wù)器進(jìn)行自行注冊。隨后,客戶端會提供服務(wù)器所定義接口的實(shí)現(xiàn)。
客戶端可通過一些標(biāo)準(zhǔn)機(jī)制向服務(wù)器進(jìn)行自行注冊。COM 為此提供了 IConnectionPointContainer 和 IConnectionPoint 接口。
COM 連接點(diǎn)服務(wù)器的客戶端可用 C++ 和 C# 托管代碼來編寫。C++ 客戶端會注冊一個類的實(shí)例,該類提供了接收器接口的實(shí)現(xiàn)。托管客戶端會注冊單個事件的委托,因而會按每個事件通知方法創(chuàng)建單個接收器,具體參考C#的互操作部分內(nèi)容。
一、連接點(diǎn)程序編寫
1、使用ATL建立組件程序。
2、添加ATL SIMPLE OBJECT,支持連接點(diǎn)事件。
注:如果當(dāng)時沒有現(xiàn)在連接點(diǎn)事件,可以在.idl文件中手動添加。比如
[
uuid(57CCB7A5-F3B6-4990-91CD-33A82E1AAA46),
helpstring("IFunEvent dispinterface")
]
dispinterface _IFunEvent
{
properties:
// 事件接口沒有任何屬性
methods:
[id(1), helpstring("方法OnResult")] HRESULT OnResult([out,retval] LONG* retval);
[id(2), helpstring("方法OnType")] HRESULT OnType([in] LONG nType);
}
3、因?yàn)橹С诌B接點(diǎn)事件,這樣將會自動生成一個 _XXXEVENT源接口。我們在其中增加想要觸發(fā)的方法。
4、選擇組件下的事件對象,彈出對話框選擇添加方法??梢岳^續(xù)添加多個方法…
5、實(shí)現(xiàn)方法(其實(shí)組件里只是做方法的申明,客戶調(diào)用時才實(shí)現(xiàn)這些方法)。實(shí)現(xiàn)時選中組件/類,按右鍵,在彈出菜單中選中implement connection....
就會產(chǎn)生CProxy_xxxEvent類,里面有Fire函數(shù)的實(shí)現(xiàn),都是自動生成的。
6、完成組件的其他接口函數(shù)。
組件的連接點(diǎn)編寫比較簡單,關(guān)鍵是如何在客戶端實(shí)現(xiàn)事件監(jiān)聽與接收。在.NET下很容易實(shí)現(xiàn)。但在VC中比較繁瑣。
二、連接點(diǎn)客戶端實(shí)現(xiàn)(VC)
1、包含“工程_i.h”頭文件,引入“工程.tlb”ole庫文件。比如:
#include "ATLDemo_i.h"
#import "ATLDemo.tlb" named_guids raw_interfaces_only
2、創(chuàng)建一個類:由_IXXXEvent派生過來。(XXX為實(shí)際事件名)
實(shí)現(xiàn)類各個虛函數(shù)重載,如果_IXXXEvent是IUnkown接口只需要重載QueryInterface、AddRef、Release函數(shù);如果_IXXXEvent是雙向接口需要重載實(shí)現(xiàn)IUnkown接口三個函數(shù)和IDispatch接口四個函數(shù)。
實(shí)現(xiàn)事件功能,通過函數(shù)、用SINK_ENTRY_INFO實(shí)現(xiàn)事件的映射、Invoke函數(shù)里面實(shí)現(xiàn)(通過事件ID)三種方法之一來實(shí)現(xiàn)。
用SINK_ENTRY_INFO實(shí)現(xiàn)事件的映射
如:
BEGIN_SINK_MAP(CEventSink)
SINK_ENTRY_INFO(1,DIID__INew01Events,DISPID_MSG,Msg,&MsgInfo)
END_SINK_MAP()
我在組件中定義了一個Msf函數(shù),所以在這里對其進(jìn)行消息隱射。然后實(shí)現(xiàn)Msg方法。
3、如何調(diào)用
3.1使用工程支持COM,使用afxoleinit或者CoInitialize/Un CoInitialize
3.2得到組件接口
3.3得到連接點(diǎn)容器,查找連接點(diǎn)。
3.4利用Advise將一個監(jiān)聽對象傳給組件,這樣當(dāng)事件發(fā)生的時候事件就會響應(yīng)。在不使用時,通過UnAdvise來斷開連接點(diǎn)事件。同時也利用AfxConnectionAdvice將監(jiān)聽對象傳給組件接口。
3.5 釋放資源。
具體代碼如下:
#pragma once
#include "ATLDemo_i.h"
#import "ATLDemo.tlb" named_guids raw_interfaces_only
class CSkin : public _IFunEvent
{
public:
CSkin(void);
~CSkin(void);
private:
DWORD m_dwRefCount;
public:
STDMETHODIMP Fire_OnType( LONG nType)
{
CString strTemp;
strTemp.Format(_T("The result is %d"), nType);
AfxMessageBox(strTemp);
return S_OK;;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject)
{
if (iid == DIID__IFunEvent)
{
m_dwRefCount++;
*ppvObject = (void *)this;
return S_OK;
}
if (iid == IID_IUnknown)
{
m_dwRefCount++;
*ppvObject = (void *)this;
return S_OK;
}
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef()
{
m_dwRefCount++;
return m_dwRefCount;
}
ULONG STDMETHODCALLTYPE Release()
{
ULONG l;
l = m_dwRefCount--;
if ( 0 == m_dwRefCount)
{
delete this;
}
return l;
}
HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
/* [out] */ __RPC__out UINT *pctinfo)
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE GetTypeInfo(
/* [in] */ UINT iTInfo,
/* [in] */ LCID lcid,
/* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo)
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE GetIDsOfNames(
/* [in] */ __RPC__in REFIID riid,
/* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,
/* [range][in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId)
{
return S_OK;
}
/* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID riid,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS *pDispParams,
/* [out] */ VARIANT *pVarResult,
/* [out] */ EXCEPINFO *pExcepInfo,
/* [out] */ UINT *puArgErr)
{
switch(dispIdMember) // 根據(jù)不同的dispIdMember,完成不同的回調(diào)函數(shù),事件函數(shù)的ID編號
{
case 2:
{
// 1st param : [in] long lValue.
VARIANT varlValue;
long lValue = 0;
VariantInit(&varlValue);
VariantClear(&varlValue);
varlValue = (pDispParams->rgvarg)[0];
lValue = V_I4(&varlValue);
Fire_OnType(lValue);
}
break;
default: break;
}
return S_OK;
}
};
#include "StdAfx.h"
#include "Skin.h"
CSkin::CSkin(void)
{
m_dwRefCount =0;
}
CSkin::~CSkin(void)
{
}
實(shí)現(xiàn)部分:
CoInitialize(NULL);
CComPtr<IFun> pFun;
HRESULT hr = pFun.CoCreateInstance(CLSID_Fun);
if(hr!=S_OK)
{
return ;
}
IConnectionPointContainer *pCPC;
hr = pFun->QueryInterface(IID_IConnectionPointContainer,(void **)&pCPC);
if(!SUCCEEDED(hr))
{
return ;
}
IConnectionPoint *pCP;
hr = pCPC->FindConnectionPoint(DIID__IFunEvent,&pCP);
if ( !SUCCEEDED(hr) )
{
return ;
}
pCPC->Release();
IUnknown *pSinkUnk;
CSkin *pSink = new CSkin();
hr = pSink->QueryInterface(IID_IUnknown,(void **)&pSinkUnk);
DWORD dwAdvise;
hr = pCP->Advise(pSinkUnk,&dwAdvise);//接收器與連接點(diǎn)建立關(guān)聯(lián)
LONG c = 0;
pFun->Add(1,5,&c);
//pCP->Unadvise(dwAdvise) //斷開連接點(diǎn)事件
pCP->Release();
pFun.Release();
CoUninitialize();
相關(guān)文章
一些C語言中字符串的算法問題解決實(shí)例小結(jié)
這篇文章主要介紹了一些C語言中字符串的算法問題解決實(shí)例小結(jié),包括將字符串轉(zhuǎn)化為int類型的數(shù)及旋轉(zhuǎn)字符串等操作,需要的朋友可以參考下2016-03-03
C語言中字符串的內(nèi)存地址操作的相關(guān)函數(shù)簡介
這篇文章主要介紹了C語言中字符串的內(nèi)存地址操作的相關(guān)函數(shù),包括bcopy()函數(shù)和bzero()函數(shù)以及bcmp()函數(shù),需要的朋友可以參考下2015-08-08
C++ 實(shí)現(xiàn)優(yōu)先隊列的簡單實(shí)例
這篇文章主要介紹了C++ 實(shí)現(xiàn)優(yōu)先隊列的簡單實(shí)例的相關(guān)資料,希望通過本文能幫助大家實(shí)現(xiàn)優(yōu)先隊列,需要的朋友可以參考下2017-08-08
C++實(shí)現(xiàn)轉(zhuǎn)置矩陣的循環(huán)
大家好,本篇文章主要講的是C++實(shí)現(xiàn)轉(zhuǎn)置矩陣的循環(huán),感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽2022-01-01
C++?Qt開發(fā)之運(yùn)用QJSON模塊解析數(shù)據(jù)
JSON(JavaScript?Object?Notation)是一種輕量級的數(shù)據(jù)交換格式,它易于人閱讀和編寫,也易于機(jī)器解析和生成,本文主要介紹了Qt如何運(yùn)用QJson組件的實(shí)現(xiàn)對JSON文本的靈活解析功能,需要的可以參考下2024-01-01

