原文:COM 连接点

CLR 完全介绍 COM 连接点 Thottam R. Sriram

来自:http://msdn.microsoft.com/zh-cn/magazine/cc163361.aspx#S1

代码下载位置: CLRInsideOut2007_09.exe (252 KB)

Browse the Code Online

目录
示例方案 
在 COM 服务器上创建连接点 
客户端 
获取 Add 和 ConnectionPoint 接口 
托管客户端 
总结 

COM 中的典型方案是让客户端对象实例化服务器对象,然后调用这些对象。然而,没有一种特殊机制的话,这些服务器对象将很难转向并回调到客户端对象。COM 连接点便提供了这种特殊机制,实现了服务器和客户端之间的双向通信。使用连接点,服务器能够在服务器上发生某些事件时调用客户端。
有了连接点,服务器可通过定义一个接口来指定它能够引发的事件。服务器上引发事件时,要采取操作的客户端会向服务器进行自行注册。随后,客户端会提供服务器所定义接口的实现。
客户端可通过一些标准机制向服务器进行自行注册。COM 为此提供了 IConnectionPointContainer 和 IConnectionPoint 接口。
COM 连接点服务器的客户端可用 C++ 和 C# 托管代码来编写。C++ 客户端会注册一个类的实例,该类提供了接收器接口的实现。托管客户端会注册单个事件的委托,因而会按每个事件通知方法创建单个接收器。在托管领域中,客户端自行注册有两种方法 — 我会在本专栏的后面部分详细介绍这两种方法。
Web 上几乎很少有事件和互操作的可用示例。在本专栏中,我将重点讨论创建活动模板库 (ATL) 连接点服务器。这包括公开 COM 方法、定义由客户端实现的事件接口,以及实现引发服务器事件的代码。我还会向您展示提供了接收器实现的示例 C++ 客户端,还有示例 C# 客户端,以及您可以注册并侦听服务器事件的两种方法。最后,我会介绍实现托管事件接收器的推荐方式。
示例方案
在我的方案中,服务器会公开 COM 方法:
HRESULT Add(int nFirst, int nSecond)

服务器还会定义 ConnectionPointContainer 和连接点,以便客户端能够向该服务器进行注册。此外,服务器会定义一个接口 _IAddEvents,该接口中有两个方法:
HRESULT AdditionStarted()
HRESULT AdditionCompleted(int nResult)

客户端会提供 _IAddEvents 的实现,并调用服务器上的 Add 方法。服务器会触发客户端上的 AdditionStarted 和 AdditionCompleted 方法,以便适时向客户端发送通知。然后,客户端会执行与这些事件相关的适当操作。
在 COM 服务器上创建连接点
在 2007 年 1 月期“CLR 完全介绍”中,我详细介绍了如何创建简单的 ATL COM 服务器(参见msdn.microsoft.com/msdnmag/issues/07/01/CLRInsideOut)。本期专栏假设您已经历了创建名为 ATLConnectionPointServer 的 ATL COM 服务器这一过程。如果还没有经历的话,您可能需要在继续之前先阅读早期的专栏。
现在,您需要定义由服务器实现的 COM 接口,并使之成为连接点。在此 COM 服务器的基础上创建连接点的过程非常简单。要执行此操作,请打开 Visual Studio® 中的“类视图”,创建一个简单的 ATL 对象。只需右键单击 ATLConnectionPointServer 并添加一个类,选择一个简单的 ATL 对象,然后将类命名为 Add。按向导逐步操作时,请务必选择“Supports: Connection Points”(支持:连接点)。
您现在便具备了可从客户端调用的服务器接口 IAdd。如果您要构建服务器,您会发现此处定义了两个接口。一个是实现 IDispatch 的 IAdd,另一个则是调度接口 _IAddEvents。
下一步是将称为 Add 的新方法添加到接口 IAdd。它会接受两个整数参数并返回一个 HRESULT。要执行此操作,请右键单击 IAdd,选择“Add Methods”(添加方法)。方法的签名为:
HRESULT Add([in] int nFirst, [in] int nSecond)

现在,请打开 ATLConnectionPointServer.idl,将方法 AdditionStarted 和 AdditionCompleted 添加到 _IAddEvents 接口,如图 1所示。
Figure 1 添加 AdditionStarted 和 AdditionCompleted
library ATLConnectionPointServerLib
{
importlib("stdole2.tlb");
[
uuid(7F45FEA6-4D7C-489C-A852-19BA8B29D8AB),
helpstring("_IAddEvents Interface")
]
dispinterface _IAddEvents
{
properties:
methods:
[id(1), helpstring("AdditionStarted")]HRESULT AdditionStarted();
[id(2), helpstring("AdditionStarted")]
HRESULT AdditionCompleted(int nResult);
};
[
uuid(15B6C26A-0416-4C8F-9533-89F318355E31),
helpstring("Add Class")
]
coclass Add
{
[default] interface IAdd;
[default, source] dispinterface _IAddEvents;
};
};

如果您在此时编译项目,则会发现一个自动生成的文件 _IAddEvents_CP.h。此文件由 ATL 生成,包含一个空的 CProxy_IAddEvents 类。这个便是在连接点完成及挂接时触发事件的类。
转到“类视图”,右键单击 CAdd,选择“添加”|“添加连接点”。在随后的向导中,选择 _IAddEvents。如果您此刻打开 _IAddEvents_CP.h 文件,它将包含为两个方法(即 Fire_AdditionStarted 和 Fire_AdditionCompleted)自动生成的代码。这是客户端接收器对象向服务器进行注册时回调到这些对象的代码。
现在,您即将完成服务器的实现过程。剩下的所有步骤便是实现服务器上的 Add 方法,并触发用于触发服务器事件的点。
打开 Add.cpp,为您添加的 Add 方法提供一个实现。该实现如下所示:
STDMETHODIMP CAdd::Add(int nFirst, int nSecond)
{
// Fire AdditionStarted event
Fire_AdditionStarted();
int nResult = nFirst + nSecond;
Sleep(1000); // simulate the addition taking a long time
// Fire AdditionCompleted event
Fire_AdditionCompleted(nResult);
return S_OK;
}

现在即可编译解决方案,您的服务器已准备就绪。
客户端
现在您可以转到客户端。我会从讨论 C++ 客户端开始,然后转到托管客户端。
客户端负责五个主要任务:
  • 它必须向您提供 _IAddEvents 接口的实现。
  • 它必须向您提供指向服务器 Add 接口的接口指针。
  • 它必须获取 Add 接口 ConnectionPoinContainer 的 ConnectionPoint,并添加接收器接口。
  • 它必须调用 Add 方法,并等待服务器事件被触发。
  • 它必须彻底关闭并退出。
要实现客户端,请打开名为 ConnectionPointClient 的新 C++ 项目,并向该项目添加新的 C++ 源文件。向该项目添加 ATLConnectionPointServer.h 和 ATLBase.h 文件。接收器会实现服务器所定义的 _IAddEvents。此接口中有两个方法:AdditionStarted 和 AdditionCompleted。这两个方法的实现如图 2 所示。
Figure 2 AdditionStarted 和 AdditionCompleted
class CSink : _IAddEvents
{
private:
DWORD       m_dwRefCount;
public:
CSink::CSink() {m_dwRefCount = 0;}
CSink::~CSink()    {}
HRESULT STDMETHODCALLTYPE AdditionStarted()
{
printf("C++ SINK: Addition started event fired ... \n");
return S_OK;
};
HRESULT STDMETHODCALLTYPE AdditionCompleted(int nResult)
{
printf("C++ SINK: Addition completed event fired ... \n");
printf("C++ SINK: Addition result: %d \n",nResult);
return S_OK;
};
...

为方便起见,我已经实现了客户端上的调度接口;示例代码提供了自动执行该操作的 ATL 客户端。此实现中的接收器只打印它已被调用的事实及添加完成后的结果。现在您的接收器已实现,可供使用。
获取 Add 和 ConnectionPoint 接口
现在已经实现了接收器,让我们来看看将向服务器注册此接收器的客户端。该客户端负责处理三个主要任务。
  • 获取指向服务器 Add 接口的接口指针。
  • 从 Add 接口获取 ConnectionPointContainer 的 ConnectionPoint。
  • 向服务器注册接收器接口。
首先,请按如下方式获取服务器的接口 IAdd:
CoInitialize(NULL);
hr = CoCreateInstance(
CLSID_Add, NULL, CLSCTX_ALL, IID_IAdd, (void **)&pAdd);
if(hr != S_OK) { return; }

然后,您必须获取服务器上的连接点,以便可以用它来注册接收器实现。要执行此操作,请按如下方式从 IAdd 接口获取 ConnectionpointContainer:
// Using the interface for add,
// query for IConnectionPointContainer interface
hr = pAdd->QueryInterface(
IID_IConnectionPointContainer,(void **)&pCPC);
if ( !SUCCEEDED(hr) ) { return; }

现在您可以到达 ConnectionPoint:
// Using the IConnectionPointContainer,
// get the IConnectionPoint interface
hr = pCPC->FindConnectionPoint(DIID__IAddEvents,&pCP);
if ( !SUCCEEDED(hr) ) { return; }

此刻,客户端必须创建其接收器实现的一个实例,并向服务器注册该实例。为此,客户端会创建接收器的一个实例,并按如下方式获取其 IUnknown 接口指针:
// Create an instance of the sink object to pass
// to the server
pSink = new CSink();
if ( NULL == pSink ) { return; }
// Get the interface pointer to CSink's IUnknown pointer, which you
// will pass to the server
hr = pSink->QueryInterface (IID_IUnknown,(void **)&pSinkUnk);
if(!SUCCEEDED(hr)) { return; }

您即将完成客户端。其余的所有步骤便是向服务器注册接收器,调用服务器,然后进行清理。客户端会向服务器注册接收器的实例:
// Pass the sink interface to the server through the Advise
hr = pCP->Advise(pSinkUnk,&dwAdvise);
if(!SUCCEEDED(hr)) { return; }

此刻,您已经向服务器注册了客户端接收器接口。
客户端会调用服务器上的 Add 方法,并将该方法所需的两个参数传递给它。添加的结果会通过 AdditionCompleted 事件返回,而不是直接从 Add 调用返回。现在请调用您获得的 IAdd 接口指针上的 Add 方法。
pAdd->Add(1, 5);

此调用应触发随之调用客户端的事件。此时,您可以通过释放您获得的所有接口来清理客户端(参见图 3)。
Figure 3 清理客户端
// Release the IConnectionPointContainer interface.
if(pCPC != NULL) pCPC->Release();
// Unadvise the event call back we registered.
if(pCP != NULL) { pCP->Unadvise(dwAdvise); }
if(pSinkUnk != NULL) { pSinkUnk->Release(); }
// Disconnect from the server.
if(pCP != NULL) { pCP->Release(); }
// Release interfaces.
if(pAdd != NULL) { pAdd->Release(); }
CoUninitialize();
return;

您最终完成了客户端。现在,您可以编译并执行客户端:
cl COMConnectionPointClient.cpp

执行时,您会看到以下输出:
C++ SINK: Addition started event fired ...
C++ SINK: Addition completed event fired ...
C++ SINK: Addition result: 6

托管客户端
现在,我想讨论一下从托管代码使用同一 ConnectionPointServer 的情况。托管客户端比 COM 客户端简单得多。实现该客户端有两种方法。首先,我将重点讨论推荐的方法。
首先,通过将 Microsoft® .NET Framework 类型库用于程序集转换器工具 tlbimp.exe,将服务器 DLL 导入到托管代码,以获得 ATLConnectionPointServerLib.dll,这一过程通过运行以下命令来实现:
tlbimp ATLConnectionPointServer.dll

您需要引用托管项目中生成的程序集,然后提供用于客户端接收器接口的实现,如图 4 所示。ManagedSink 类实现了 _IAddEvents 接口中所定义的两个方法,AdditionStarted 和 AdditionCompleted。完成后,您的接收器事件处理程序便完成了,可供使用(与 COM 客户端比起来,它看上去几乎太简单了,对吧?)。
Figure 4 提供接收器接口的实现
public class ManagedSink :_IAddEvents
{
public void AdditionStarted()
{
Console.WriteLine("C# SINK: Addition started event fired ...");
}
public void AdditionCompleted(int nResult)
{
Console.WriteLine("C# SINK: Addition completed event fired ...");
Console.WriteLine("C# SINK: Addition result: {0}", nResult);
return;
}
};

与 COM 客户端一样,您必须向服务器注册接收器,以便服务器能够在触发事件时调用该接收器。然而,托管客户端向服务器进行自行注册的方式会有所不同。
COM 客户端向服务器注册了已实现接口 _IAddEvents 的接收器对象实例。提醒一下,以下调用注册了 COM 客户端:
// Pass the sink interface to the server through the Advise
hr = pCP->Advise(pSinkUnk,&dwAdvise);
if(!SUCCEEDED(hr)) { return;}

使用托管客户端,您可以向服务器将单个方法作为委托注册。要实现这一点,您需要创建接收器对象的实例:
ManagedSink ms = new ManagedSink();

创建服务器对象实例,并单独添加 AdditionStarted 和 AdditionCompleted 事件处理程序,如下所示:
AddClass a = new AddClass();
a.AdditionStarted += ms.AdditionStarted;
a.AdditionCompleted += ms.AdditionCompleted;

客户端会向服务器为每个事件处理程序注册两个不同的接口。要添加到客户端委托的先前调用会在客户端的运行库可调用包装 (RCW) 上添加一个引用计数。调用完成后,必须通过删除事件处理程序来释放该引用计数,如下所示:
a.Add(1, 5);
a.AdditionStarted -= ms.AdditionStarted;
a.AdditionCompleted -= ms.AdditionCompleted;

最后,编译 ManagedClient.cs:
csc /r:ATLConnectionPointServerLib.dll ManagedClient.cs

并运行可执行文件。您会看到以下输出:
C# SINK: Addition started event fired ...
C# SINK: Addition completed event fired ...
C# SINK: Addition result: 6

总结
编写用于 ATL 调度接口的客户端实现稍微有点复杂。我此处讨论的示例特意通过使用其本身的调用实现来解决该复杂性。
我要感谢 Cosmin Radu、Ladi Prosek、Mason Bendixen、Varun Sekhri 和 Claudio Caldato,感谢他们为本期专栏的制作提供帮助并提出宝贵意见。

转载于:https://www.cnblogs.com/zhehan54/p/5909760.html

【转载】COM 连接点相关推荐

  1. 3D中的OBJ文件格式详解(转载)

    OBJ文件是Alias|Wavefront公司为它的一套基于工作站的3D建模和动画软件"Advanced Visualizer"开发的一种标准3D模型文件格式,很适合用于3D软件模 ...

  2. 在Windows下创建硬连接和文件夹连接点的工具

    之前提到过在Windows[NTFS]下面创建硬连接的命令行工具(请看<简化创建硬连接命令>),有没有更方便的方法呢?请看这次推荐的几个工具:(这三个工具都可以到http://schina ...

  3. Spring框架介绍及使用(转载)

    原文链接 Spring框架-控制反转(IOC) 1 Spring框架概述 1.1 什么是Spring Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由R ...

  4. Visio 快捷大全(转载)

    Visio 快捷大全 时间:2011-11-20 11:59:48  来源:  作者: 非常好用的制图软件.尤其在系统结构设计方面,能快速勾画出系统结构图. "帮助"任务窗格和&q ...

  5. OpenGL帧缓存对象(FBO:Frame Buffer Object)(转载)

    原文地址http://www.songho.ca/opengl/gl_fbo.html 但有改动. OpenGL Frame BufferObject(FBO) Overview: 在OpenGL渲染 ...

  6. 基于注解的Spring AOP的配置和使用--转载

    AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...

  7. [转载]Office Visio快捷键

    "帮助"任务窗格和"帮助"窗口 使用"帮助"任务窗格和"帮助"窗口 通过"帮助"任务窗格,您可以访问 ...

  8. WIN7系统中连接点(Junction Points)

    与xp.win2003相比,win7.win2008(包括win2008R2)系统中,存放用户数据(user data)和系统数据(system data)的默认文件夹位置发生变化.例如,在xp中存放 ...

  9. 转载:Spring AOP (下)

    昨天记录了Spring AOP学习的一部分(http://www.cnblogs.com/yanbincn/archive/2012/08/13/2635413.html),本来是想一口气梳理完的.但 ...

最新文章

  1. 设计师不应该错过的响应式设计框架(含优缺点分析)
  2. python Selenium 常见操作 元素定位
  3. (常用API)正则表达式语法规则
  4. Linux目录遍历实现,列出目录下文件,可使用部分参数
  5. SpringBoot项目新手——问题疑惑及解决笔记
  6. java 数据结构 迷宫_JAVA数据结构与算法之递归(一)~ 迷宫问题
  7. dede mysql语句_让dede运行php代码和mysql语句
  8. 吴恩达深度学习 —— 2.18(选修)逻辑回归损失函数的解释
  9. spring boot—默认日志框架配置
  10. day27 粘包及粘包的解决方案
  11. 如何在《救赎之路》中使用CPU粒子效果
  12. 创建数据库常用SQL语句
  13. 兄弟打印机内存已满清零方法_兄弟打印机清零方法大全
  14. 学生DW网页设计作业成品——电商购物网站设计(55页) 电商网页设计制作 简单静态HTML网页作品 购物网页作业成品 学生商城网站模板
  15. Visio连接线设置箭头形状失效
  16. 《东周列国志》第八十七回 说秦君卫鞅变法 辞鬼谷孙膑下山
  17. 三维地理信息系统空间的可视分析
  18. php身份证识别ORC
  19. 读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字。
  20. 135编辑器嵌入html,135编辑器教程|三步教你搞定表格样式

热门文章

  1. webstorm怎么跑项目_快讯!明年厦门中考体育项目定了!初三家长抽的!其他地市抽到啥?...
  2. SpringData JPA条件查询、排序、分页查询
  3. java配置文件中的plugin,启用ContextReplacementPlugin以忽略webpack中的配置和测试设置文件...
  4. java inflaterinputstream_java.util.zip.InflaterInputStream.available()方法示例
  5. 互动赠书 | 云上云下K8s多集群如何实现集群管理和安全治理的一致体验?
  6. 基于 KubeVela 与 Kubernetes 打造“无限能力”的开放 PaaS
  7. 基于 K8s 做应用发布的工具那么多, 阿里为啥选择灰姑娘般的 Tekton ?
  8. 曾宝仪和机器人_你愿意和机器人成为终身伴侣吗?
  9. linux设置双屏拼接_双屏办公,用起来到底有多爽
  10. tornado项目搭建_Day71-73 BBS项目(1)