原文地址: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(免费版)翻译