用ATL建立轻量级的COM对象(八)

来源:互联网 发布:unity3d ai插件 编辑:程序博客网 时间:2024/05/09 17:10

第一部分:为什么要使用ATL。

第二部分:起步篇。

第三部分:实现IUnknown。

第四部分:实现接口。

第五部分:不要过分抽象。

第六部分:输出你的类。

第七部分:ATL和注册表。

连接

COM 编程最单调乏味的一个方面是使用连接点来支持 outbound 接口。IConnectionPoint/IConnectionPointContainer 的设计好像是专门用来解决这个问题的,但是经验证明,不论是在性能方面,还是在易用性方面,它还是存在不足之处。ATL为每一个这样的接口提供了缺省的实现类,多少解决了一些易用性问题。

要理解ATL如何实现连结点,最容易的方式是看例子:假设定义了如下的 outbound 接口:

1.interface IPageSink : IUnknown {
2.HRESULT OnPageReceived(void);
3.}
4.interface IStopSink : IUnknown {
5.HRESULT OnShutdown(void);
6.}

为了支持这两个 outbound 接口,下面的ATL代码已经足够:

01.class CPager
02.public CComObjectRoot,
03.public CComCoClass,
04.public IPager,
05.public IConnectionPointContainerImpl,
06.public IConnectionPointImpl,
07.public IConnectionPointImpl
08.{
09.BEGIN_COM_MAP(CPager)
10.COM_INTERFACE_ENTRY(IPager)
11.COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
12.END_COM_MAP()
13. 
14.BEGIN_CONNECTION_POINT_MAP(CPager)
15.CONNECTION_POINT_ENTRY(IID_IPageSink)
16.CONNECTION_POINT_ENTRY(IID_IStopSink)
17.END_CONNECTION_POINT_MAP()
18. 
19.};

大多数有经验的 COM 程序员首先注意到的是 CPager 类从一个接口(IConnectionPoint)派生的,这个接口并不作为 COM 本身的一部分提供。为了实现这种诀窍,ATL 类 IConnectionPointImpl 不从接口 IConnectionPoint 派生,而是象 IConnectionPoint 那样以相同的顺序定义它的虚函数。

其次,为了防止每一个基类继承主对象的 QueryInterface 实现,IConnectionPointImpl中第一个虚函数不是QueryInterface。而是一个类型兼容的方法,它叫做 LocCPQueryInterface,这个方法只针对 IID_IConnectionPoint 和 IID_IUnknown。它除了涉及允许完全基于多线程实现外,还有许多深奥的窍门在里面。

对象的 FindConnectionPoint 方法实现使用由 ATL中 CONNECTION_POINT 宏定义的连接点映射。此映射是对象中的一个 DWORD表,表示与 IConnectionPoint 实现相对应的偏移量。FindConnectionPoint 遍历此偏移量数组,询问每一个所碰到的连接点,看看此连接是否拥有所请求的接口。

上述例子建立了一个有效的对象实现,它支持作为 outbound 接口的 IStopSink 和 IPageSink 。但是,为了调用outbound 接口,你需要存取由 IConnectionPointImpl 类操纵的接口指针向量,并自己手动模拟多点传送:

1.typedef IConnectionPointImpl base;
2.for (IUnknown** pp = base::m_vec.begin();
3.pp < base::m_vec.end();
4.pp++)
5.if (*pp)
6.((IPageSink*)(*pp))->OnPageRecieved();

编写多点传送例程十分繁琐。所幸的是,ATL提供了 Visual Studio 组件―― ATL 代理产生器(ATL Proxy Generator),(如下图五)它们会读取接口的类型库描述并产生 IConnectionPointImpl 派生类,为每一个 outbound 方法添加适当的 Fire 例程。连结点代理便被产生出来。

图五 ATL 代理产生器 

这个类的定义应该像下面这样:

01.class CPager
02.public CComObjectRoot,
03.public CComCoClass,
04.public IPager,
05.public IConnectionPointContainerImpl,
06.public CProxyIPageSink,
07.public CProxyIStopSink
08.{
09.BEGIN_COM_MAP(CPager)
10.COM_INTERFACE_ENTRY(IPager)
11.COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
12.END_COM_MAP()
13. 
14.BEGIN_CONNECTION_POINT_MAP(CPager)
15.CONNECTION_POINT_ENTRY(IID_IPageSink)
16.CONNECTION_POINT_ENTRY(IID_IStopSink)
17.END_CONNECTION_POINT_MAP()
18.};

为了发送出境方法通知,你只要调用适当的Fire_XXX方法即可:

1.STDMETHODIMP CPager::SendMessage(LPCOLESTR pwsz) {
2.// send outbound notifications
3.HRESULT hr = Fire_OnPageRecieved();
4.// process normally
5.return hr;
6.}

 机器产生代理的一个限制是必须要 outbound 接口的类型库定义。对于大量的 COM 接口而言,这是不可能的,因为类型库在转换方面与 IDL 特性背道而驰。对于更复杂的接口,你可以从机器所产生的代理开始并修改这些代码来进行尝试。

后记

本文概括地讨论了 ATL 核心体系结构,主要针对 ATL 中使用,同时也是 ATL 用户使用的基本编程风格。实际上本文中所讨论的和涉及的内容特性都是基于 ATL1.1 版本的,某些内容在 ATL2.0 中稍有改动。本文没有包含有关ActiveX 控件接口的缺省实现方面的内容,它们被加到了 ATL2.0 版本中,遵循与 ATL1.1 同样的编程哲学。只要你常常推敲 ATL 源代码,这些接口会很容易理解。

在与同事和朋友讨论 ATL 时,我发现大多数人的感触是针对 ATL 的设计。有一小部分人觉得它非常棒。另外一小部分人觉得它复杂而不可思议。但大多数人(包括我自己)的感觉是双重的,优劣兼而有之。幸运的是 ATL 编程是一个非常"量入为出"的过程。只要你去学总会有收获,所以初学者需要努力而为之。(全文完)


http://www.vckbase.com/index.php/wv/191.html

原创粉丝点击