COM的通信原理
1.通信模型:一般,我們只使用客戶端程序到組件的通信,並且這種通信是通過組件的接口來實現的。現在,我們講一下伺服器到客戶端之間如何打開一個雙向通信通道,並提供一個功能更加強大的通信環境。按下面的方法可以提供一個具備回調功能(或稱通知)的組件:
1)在一個組件中描述幾個接口,其中一部分接口由組件實現(如IMath),一部分接口則由客戶端程序實現(如ICallback)。
2)在客戶端程序中,使用自己喜歡的技術實現一個接口,並由組件來描述(如iCallback)。
3)組件在其中的一個入站接口上實現一個方法(如IMath::Advise),客戶端程序可以通過該方法傳送它的一個接口指針(ICallback)。
4)然後組件通過客戶端實現的接口調用接口方法,為客戶端程序提供通知消息。
2.引入和引出接口
COM使用incoming interface(引入接口)和outing interface(引出接口)兩個術語,來描述組件可以支持的兩種不同類型的接口。一個引入接口是指由組件實現的接口,如IMath是個引入接口,因為它是由你的組件來實現的。一個引出接口是指在組件的類型庫中描述的接口,但是它實際上是由math組件的客戶端程序實現的。
3.示例
interface ICallback:IDispatch
[
[id(1),helpstring("顯示求和結果")] HRESULT Show([in] long sum);
]
interface IMath:IDispatch
[
[id(1),helpstring("求和並顯示")] HRESULT Add([in] long num1,[in] long num2,[out,retval] long* ret);
[id(2),helpstring("添加引出接口") HRESULT Advise([in] ICallback* pCallback);
[id(3),helpstring("釋放接口") HRESULT Unadvise();
]
class CMath:IMath
{
CComPtr<ICallback> m_pCallback;
STDMETHODIMP Add(long num1,long num2)
{
long ret=num1+num2;
if(m_pCallback)
m_pCallback->Show(ret);
return S_OK;
}
STDMETHODIMP Advise(ICallback* pCallback)
{
m_pCallback=pCallback;
pCallback->AddRef();
return S_OK;
}
STDMETHODIMP Unadvise()
{
m_pCallback->Release();
m_pCallback=0;
return S_OK;
}
}
客戶端程序首先實現ICallback接口中的函數,並把ICallback的實現類通過Advise傳給CMath,這樣當進行加法時,就能通知客戶端了,這就是COM的通信原理。
ATL通信方法
ATL提供了IDispEventSimpleImpl和IDispEventImpl兩個模板類,這兩個模板類可用於在 ATL 類中提供連接點接收器支持,為事件調度接口提供了實現,我們只需要對要接收的事件方法提供實現。這些連接點接收器是用事件接收映射(由類提供)來映射的。
1.若要正確地實現類的連接點接收器,必須完成以下步驟:
1)為每個外部對象導入類型庫 (如:#import "progid:SendEvent.MyMath" raw_interfaces_only, no_namespace, named_guids),
2)繼承 IDispEventImpl接口(如public:IDispEventSimpleImpl<1,CSumDlg,&DIID_IMathEvents>),
或 繼承IDispEventSimpleImpl 接口(如IDispEventImpl<1,CSumDlg,&DIID_IMathEvents,&LIBID_SendEvent,1,0>) 。
3)聲明事件接收映射 ,在類中添加BEGIN_SINK_MAP(classname)、END_SINK_MAP()宏,
4)IDispEventSimpleImpl都必須添加一個宏SINK_ENTRY_INFO去實現事件接收映射。如:
BEGIN_SINK_MAP(CSumDlg)
SINK_ENTRY_INFO(1,DIID_IMathEvents,1,OnShow,&ShowInfo)
END_SINK_MAP()
5)IDispEventImpl都必須添加一個宏SINK_ENTRY或SINK_ENTRY_EX去實現事件接收映射。如
BEGIN_SINK_MAP(CSumDlg)
SINK_ENTRY_EX(1,DIID_IMathEvents,1,OnShow)
END_SINK_MAP()
6)實現事件處理函數,如實現OnShow函數。
7)通知(調用DispEventAdvise與數據源建立連接)。
8)和取消通知連接點 (調用DispEventUnadvise斷開連接)。
2.詳細解析
1)IDispEventImpl繼承於IDispEventSimpleImpl,他們的大部分功能是相同的,區別僅在於IDispEventImp是從類型庫中獲取接口信息,而IDispEventSimpleImp是通過一個指向SINK_ENTRY_INFO結構體的指針獲得事件信息。
2)IDispEventImpl和IDispEventSimpleImpl的參數分別為
IDispEventImpl<
>
IDispEventSimpleImpl<
>
其中,
T:從IDispEventImpl/ IDispEventSimpleImpl派生的類;
3)宏的操作
事件接收映射必須以BEGIN_SINK_MAP(class)開頭,以END_SINK_MAP()結尾,其中class是接收事件的類。
SINK_ENTRY_INFO、SINK_ENTRY_INFO和SINK_ENTRY的關係為
SINK_ENTRY_INFO(id, iid, dispid, fn, info)
#define SINK_ENTRY_EX(id, iid, dispid, fn) SINK_ENTRY_INFO(id, iid, dispid, fn, NULL)
#define SINK_ENTRY(id, dispid, fn) SINK_ENTRY_EX(id, IID_NULL, dispid, fn)
其中,
id:唯一標識數據源對象的標誌,與模板類的第一個參數對應;
iid::要接收的事件調度接口的DIID指針;
dispid:事件的調度ID,與接口中方法的ID對應;
fn:事件處理函數;
info:SINK_ENTRY_INFO結構體的指針,主要包括事件的參數和返回值信息。