分享

COM/ATL通信原理

 吴雨虹2kzpi83a 2020-08-23

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结构体的指针,主要包括事件的参数和返回值信息。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多