[Windows翻译]COM连接点介绍

172 阅读5分钟

原文地址:devblogs.microsoft.com/oldnewthing…

原文作者:devblogs.microsoft.com/oldnewthing

发布时间:2013年6月11日

上一次,我们看到了如何列举所有的Internet Explorer和资源管理器窗口,并查看它们正在查看什么。但该程序打印的是静态信息。如果用户点击到另一个网页或导航到不同的文件夹,它并没有跟踪窗口的变化。

为了挂上这一点,我们需要了解连接点模型和事件在调度接口中的表达方式。首先,我们来看看连接点模型。当我第一次见到这些主题时,我感到很困惑(部分原因是我没有在思想上把它们分成两个主题,而是把它当作一个大主题),所以我将花几天时间试图解释它是如何工作的,然后在本周晚些时候,我们将真正把事情挂起来。(而实际勾搭起来比解释起来要容易得多)。

今天,连接点模型。

假设你有一个widget,它可以有多个客户端。客户端可以通过调用widget上的方法与widget进行通信,比如IWidget::SetColor.但是widget如何与它的客户端通信呢?好吧,因为这是COM,所以你首先需要的是一个接口,比如说,IWidgetClient。我们的想法是,每个客户端实现IWidgetClient,当widget需要通知每个客户端颜色改变时,它可以在每个客户端上调用IWidgetClient::OnColorChanged。每一个客户端都可以向小组件注册,以获得通知。

注册机制标准化的COM接口是IConnectionPoint。连接点在小组件和它的所有客户端之间充当中间人。每当widget需要通知所有客户端时,它都会告诉连接点去做。

Widget -> Connection Point -> Client A, Client B, Client C

客户端通过调用IConnectionPoint::Advise注册连接点,并通过调用IConnectionPoint::Unadvise取消注册。

好吧,这很好,但客户端如何找到连接点,以便向它注册呢?

小组件公开了一个名为IConnectionPointContainer的接口,它提供了对对象的连接点的访问。客户端可以调用IConnectionPointContainer::FindConnectionPoint方法来获取对特定连接点的访问。

下面是各部分如何组合在一起的。

// error checking elided for expository purposes

void IUnknown_FindConnectionPoint(IUnknown *punk,
                                  REFIID riid,
                                  IConnectionPoint **ppcp)
{
 // get the IConnectionPointContainer interface
 CComQIPtr<IConnectionPointContainer> spcpc(punk);


 // Locate the connection point
 spcpc->FindConnectionPoint(riid,  ppcp);
}


class CClient : public IWidgetClient
{
…
 IWidget *m_pWidget;
 DWORD m_dwCookie;
};


CClient::RegisterWidgetClient()
{
 // Find the IWidgetClient connection point
 CComPtr<IConnectionPoint> spcp;
 IUnknown_FindConnectionPoint(m_pWidget,
                              IID_IWidgetClient, &spcp);


 // register with it
 spcp->Advise(this, &m_dwCookie);
}


CClient::UnregisterWidgetClient()
{
 // Find the IWidgetClient connection point
 CComPtr<IConnectionPoint> spcp;
 IUnknown_FindConnectionPoint(m_pWidget,
                              IID_IWidgetClient, &spcp);


 // unregister from it
 spcp->Unadvise(m_dwCookie);
}

注册为widget客户端后,CClient对象将接收其IWidgetClient上的方法调用,直到它取消注册。

现在widget和客户端有了双向通信。如果客户端想要发起通信,它可以调用IWidget上的方法。如果widget想要发起通信,它可以调用IWidgetClient上的一个方法。

请注意,我们已经创建了一个巨大的循环引用。小组件有一个对它的连接点的引用(所以它可以告诉它向所有客户端发出通知),连接点有一个对小组件客户端的引用(所以它可以把通知转发下去),小组件客户端在它的m_pWidget成员中有一个对小组件的引用。为了打破这个循环,当你不再对接收小组件通知感兴趣时,你必须记得显式调用UnregisterWidgetClient。

请注意,即使上图中的箭头从左到右流动(从小组件到客户端),这并不意味着信息流是严格的从左到右。你可以通过返回值或输出参数在另一个方向传递信息。

例如,在IWidgetClient接口上可能有一个叫做GetColor的方法。

interface IWidgetClient : IUnknown
{
 …
 HRESULT GetColor([out] COLORREF *pclr);
 …
};

由于可以有多个客户端,小组件需要有某种规则来决定哪个客户端可以选择颜色。它可能会决定依次向每个客户端询问颜色,直到其中一个客户端返回 S_OK,然后使用该客户端的颜色,不再通知其他客户端。

或者有一个叫做OnSave的方法。

interface IWidgetClient : IUnknown
{
 …
 HRESULT OnSave([in] IPropertyStorage *pps);
 …
};

这里的约定可能是,所有的客户端都会收到保存操作的通知,他们可以在传递通知的同时,将任何额外的信息写入IPropertyStorage。

这些只是例子。你可以随意编造你自己的例子。重点是,仅仅因为箭头从小组件到客户端,并不意味着信息不能从另一个方向流回。

大多数情况下,你有一个简单的案例,一个widget会暴露一个单一的连接点。在这种情况下,IConnectionPointContainer的通用性可能看起来没有必要。但它允许您以后添加新的连接点。例如,您可能为不同类型的客户端拥有多个客户端接口。您可以为那些只对颜色变化感兴趣的客户提供IWidgetColorClient,为那些只对监视widget的网络活动感兴趣的客户提供IWidgetNetworkClient。

或者你原本并不打算有多个连接点,但是在你的产品的第二个版本中,你想给IWidgetClient添加额外的方法,所以你需要创建IWidgetClient2,这意味着你也需要为它新建一个连接点。

下一次,看看客户端接口是调度接口的特殊情况。


通过www.DeepL.com/Translator(免费版)翻译