技术标签: 技术笔记 exe dll methods 服务器 interface properties
Connection Point Internals Working with ATL 8
COM ATL 连接点 进程间通信 代理存根 双接口 自定义接口
(1) 介绍连接点
(2) 实现一个简单的连接点
(3) 用双接口实现连接点来进行进程间通信
(4) 用自定义接口实现连接点来进行进程间通信
(5) 小结
什么是连接点?描述一个概念不是我的长项,所以我就从《深入解析ATL》(Brent Rector、Chris Shells著 潘爱民 新语 译)上摘抄了三段来充数。 J
术语连接点(connection point)指的是一种逻辑上的反馈机制, 这种反馈机制允许对象暴露其“调用一个或者多个指定接口”的能力。
一个连接有两个部分:对指定接口的方法产生调用的对象:被称作源(source)或者连接点(connection point);以及实现该接口(即接收调用的接口)的对象,被称作接收对象(sink object) ,有些书上也叫接收器 。
虽然这个连接点协议在套间内部使用是可以被接受的,但是对于跨越套间边界的使用是非常低效的(当考虑到来回调用开销的时候)。
术语一类的东西向来让我找不着北,要对概念理解的更深入,就必需多实践,在实践中发现问题, 解决问题。吃一堑,长一智嘛 J
在实现连接点之前,相信还是十分有必要简单的介绍一下回调接口的概念和方法。回调接口的原理和使用很简单,和回调函数差不多,唯一不同的是回调函数只有一个,而作为函数集合的接口,回调接口其实就是回调函数的集合。大家如果不明白回调接口的话,推荐大家去看看杨老师的文章,明白之后再回过头来看连接点 J 。(谁是杨老师?什么!你连杨老师都不知道?这,这,这 … 赶紧去搜索一下 J )
好了,第一个例子我想让代码尽量的少,以方便我们看清连接点的本质,所以我要用自定义接口来实现。
我要实现的例子很简单,
有个组件叫Teacher实现了ISpeaker接口,并实现一个连接点_ISpeakerEvents
另一个组件叫Student,实现了_ISpeakerEvents这个连接点的接收器。
终于可以动手写程序了--------- J J J
(1)首先新建一个工程,解决方案叫SimpleSpeaker,工程名称Teacher
图2-1
(2)选择工程属性,直接用默认的配置,直接点“完成” J
(关于各选项的作用我这里就不介绍了,相信大家应该都在介绍COM的书上或多或少的了解过)
图2-2
(3)我们将看到下图的一个程序类图。紧接着,在类视图中添加一个类。
图2-3
(4)插入一个ATL简单对象,简称:Speaker,并“下一步”,在选项中我们用的是自定义接口,并勾上了连接点,选好之后点“完成”。 J
图2-4
图2-5
(5)ATL默认的连接点接口定义是一个双接口,而我想要的是一个自定义接口的连接点,所以需要在IDL文件中做下修改:
修改好后最后在解决方案管理器里面先单独编译一下.idl文件,这是个好的习惯。
(6)好了,我们可以添加方法了,分别给ISpeaker和_ISpeakerEvents添加一个方法,老师有了一个Speak方法(说话嘛),学生有了一个OnHearSpeak(听到老师讲)。
添加后的IDL如下:
这里要注意,如果你上一步的IDL修改正确,给_ISpeakerEvents添加的接口是不会有id属性的,如果你发现你的OnHearSpeak前面有id什么的,那说明你上一步做漏了什么地方。
顺序的话,我们就可以开始用向导帮我们实现连接点了。
(7)在类视图中右键选中CSpeaker类->添加->添加连接点
图2-6
把源接口选到里的_ISpeakerEvents选到“实现连接点”后点“完成”,我们就可以从类视图中打开 CProxy_ISpeakerEvents <T>类,看看向导帮我们生成了什么代码。
图2-7
(7)我把对代码的分析写在代码里了,大家看看应该就明白了。
(8)编译一下整个工程,成功,恭喜你连接器已经实现好了。
(9)我们来实现一个简单的客户端,一个控件台程序,够简单了吧。
在公共头文件那勾上ATL就实现起来就更简单了 J
图2-8
点“完成”后就可以添加代码了,添加一个C++类,头文件如下
实现细节我就不多说,大家可以看代码。其实就是实现了一个类,一个继承了_ISpeakerEvents接口的类,因为是个接口的缘故,他需要实现一下QueryInterface、AddRef、Release三个方法。
为什么Advise会失败?
第一个例子的Teacher组件是一个DLL,也就是一个进程内组件。那么如果我们在实现上面那个例子的第(2)步选择服务器类型时选的是EXE或者是服务,其它都不变,大家觉得会不会成功呢?如果你试过之后,应该会发现,在AtlAdvise的时候会出错,为什么呢?
这是我为什么想写这篇文章的初衷。我发现这个问题的时候,跟到ATL的源码,发现在atlcom.h中的
STDMETHODIMP IConnectionPointImpl < T , piid , CDV >:: Advise ( IUnknown * pUnkSink , DWORD * pdwCookie )
这个函数调用的时候,会去QueryInterface一下_ISpeakerEvents接口,就是检查一下接收器是否实现了连接点接口,函数返回失败――没找到这个接口的实现。很奇怪的是明明我们的Listener类继承了_ISpeakerEvents,这到底是为什么呢?原因出在跨越了进程,或者说是跨越了套间(呵,套间这东西和连接点一样,比较拗口,下次有空再解释 J )。
我们都知道在COM中,存在的叫做代理跟存根的东西,这两个东西是做什么的呢?在Windows系统中每个进程都有自己的地址空间,不同进程间的指针参数是不能直接传递的,因为在进程A中指定0x00400001的指针,到了进程B中所指的东西就不是原来你想要的了,所以需要用到列集(marshaling)。又是概念,简单的说列集就是对参数进行调整――使得进程A调用进程B的数据像是在自己的进程空间里一样。代理存根正是用来做列集(也就是调整)这个事情的,而标准的代理存根是依赖于IDL文件的,也就是说,如果你不想自己实现代理存根,那你就必需实现IDL,下在这句话很重要,请一定记住:
要传递的接口指针,必需用IDL定义,只有这样,标准的代理存根才能把接口指针所指的内容正确的调整到其它进程空间,否则你就自己实现代理跟存根。
拿到上面的例子中说明,就是Listener这个接口(实现了IUnknown的就是接口,而且我们也确实是把他当成了一个IUnknown来用的)必需用IDL定义之后,代理存根才能正确的调整参数,才不会advise失败,像上面那样只是简单的继承,接口指针传递到另一个进程空间的时候,他所表示的意义已经完全不同了,所以才会出现QueryInterface找不到_ISpeakerEvents的情况。
说的比较拗口,不知道大家明白没有,下面我就开始介绍第二个例子,用双接口实现连接点进行进程间通信:
我们将创建三个工程,Teacher.exe、Student.exe、Client.exe,好了,我们开始吧。 J
(1) 我们首先我实现Teacher,这是一个ATL 进程外服务器:
第一步,创建工程:
图 3-1
工程设置如下:
图 3-2
第二步,我们创建ISpeaker接口,添加一个“ATL简单对象”Speaker,接口设置如下,记得勾上连接点。
图 3-3
第三步,添加接口方法
点击完成之后,我们还必需对生成的idl进行修改。分别给ISpeaker和_ISpeakerEvents加个方法。
ISpeaker你可以用向导来生成,这样向导能帮你在Speaker.h和Speaker.cpp中生成对应的函数生明。_ISpeakerEvents的方法就得自己在idl里面手动添加了 J
第四步,用向导生成连接点实现代码
修改好idl后,编译一下idl文件,确认idl编译成功后,我们右键连接点的实现类,选择“添加”-》添加连接点。
图 3-4
图 3-5
按照图 3-4和3-5,向导会帮我们生成好连接点实现类,在 _ISpeakerEvents_CP.h 文件中,并在Speaker.h中为我们添加连接点相关的代码,我这里就不细讲,大家自己看一下吧。
第五步,调用连接点的方法,Teacher工程完成。
用向导实现好连接点后,我们就要以使用连接点的方法了:
当然这里的Fire_OnTalk只是一个空架子,真正的实现,也就是连接点的接收器,我们下面来实现。
(2)创建Student工程,这也是一个ATL进程外服务器。
第一步,创建工程,参考图 3-1和3-2。
第二步,创建IListener接口
用默认的选项,直接点击完成
第三步,实现接收器,这里需要我们手动添加代码:
接着给IListener接口添加两个方法,用来建立和断开连接点:
SpeakerEventsImpl :: DispEventAdvise 和 SpeakerEventsImpl :: DispEventUnadvise 。
实现上很简单,就是分别调用了
这里要说的是,这两个函数大家如果跟代码进去的话就会发现,他们其实就是对API函数AtlAdvise和AtlUnadvise的一层包装。
好了,这样,我们的Student也实现了。
(2) 实现调用两个ATL服务器的客户端Client
第一步,创建一个控制台应用程序,加上“ATL”公共头文件
第二步,实现调用两个接口,为了让代码看上去少一点,这里我把返回值判断给去掉了,大家可不要这样啊 J
最后一步,编译、运行Client工程,成功 J
在这个例子中,我们的连接点是实现在Teacher.exe中的,接收器是实现在Student.exe中的,两个进程用连接点正确的进行了通信,那如果我们想用自定义接口来实现进程间通信吗?继续往下看 J
我们终于要来解决“2-实现一个简单的连接点””一节最后留下的问题了。
连接点进行进程间通信的关键在于 代理/存根,只要进程间的通信是通过 代理和存根的,COM就会帮我们隐藏进程间的通信,也就是说进程间通信对两个COM组件来说是透明的。好了,费话少说,我们开始。
(1)Teacher
Teacher的实现我们可以参照 “2-实现一个简单的连接点”,唯一的不同在于上面是一个DLL,这次我们要让他是一个进程外组件,是个EXE。大家在创建工程时的工程设置里注意一下就可以了,其它步骤一模一样。
(2)Student
第一步,创建一个进程外组件,和上面一样。
第二步,添加IListener接口,和上面一样,我们用向导添加一个“ATL简单对象”。
第三步,这回不一样了,我们要做的是去teacher.idl中把_ISpeakerEvents的接口定义复制到Student.idl中。
然后在idl的最后找到coclass Listener,让CListener类也实现_ISpeakerEvents接口。
第四步,接着我们当然是要在CListener类中实现_ISpeakerEvents接口
在Listener.h文件中添加下面三行后,在cpp文件中实现OnHearSpeak
第五步,实现创建和断开连接点的两个方法,方法同上。
第六步,哈哈,这里可以注意了,这里是用自定义接口实现连接点的一个小技巧,是我从微软的例子那学来的,不过稍微有点不同。
去Teacher.idl中把_ISpeakerEvent相关的定义注释掉
_ISpeakerEvents_CP.h 就编译不过了,所以,你要在 _ISpeakerEvents_CP.h 文件头上加上对 Student .h的包含,哈哈,是不是很假呢?
Client
和用双接口实现时一模一样,我就不细讲了,大家看代码吧。
多看看ATL源码对我们很有帮助。
P s:源代码编译说明,我是用vs2005 sp1实现的。因为工程之间有相互依赖性,我比较懒,没设工程依赖,如果大家在编译时提示错误说:发现少文件或lib,那肯定是你少编译了某个工程,如ps工程什么的。
Gook luck J
文章浏览阅读698次。Spring Boot 应用集成 Activiti 工作流引擎_springboot整合activiti工作流
文章浏览阅读1w次。Linux/Openwrt路由安装配置UPNP服务提高迅雷下载速度发布时间:September 7, 2012 // 分类:OpenWrt // 1 Comment路由器下电脑为实现互联网端到端的连接需要配置DNAT(端口映射),UPNP就相当于自动化DNAT的实现,路由和客户端软件都需支持UPNP。Openwrt路由下安装UPNP服务:_upnp安装
文章浏览阅读7.9k次。麒麟985芯片是自身就有5G通讯能力,而骁龙的865只有和X55芯片结合时才会有5G的通讯能力,麒麟985采用领先的7nm5GSoC,拥有旗舰的5G体验,领先的八核CPU,全新的Mail-G77 GPU,领先的双核NPU。我用的手机就是活动时8折抢购的 点击开抢 https://shouji.jd.com从数据上看,在5G速度方面比骁龙865+X55组合形式下行速度快40%,上行速度快75%;在密集场所下载速率比后者快13%;告诉场景下下载速率比后者快31%;高铁场景下下载速度比后者快3倍;能效比是后者的_骁龙865 麒麟985
文章浏览阅读7.5k次,点赞5次,收藏24次。概念LIB有两种:一种是静态库,比如C-Runtime库,这种LIB中有函数的实现代码,一般用在静态连编上,它是将LIB中的代码加入目标模块(EXE或者DLL)文件中,所以链接好了之后,LIB文件就没有用了。 一种LIB是和DLL配合使用的,里面没有代码,代码在DLL中,这种LIB是用在静态调用DLL上的,所以起的作用也是链接作用,链接完成了,LIB也没用了。至于动态调用DLL的话,根本...
文章浏览阅读309次。学了zynq一段时间,一上来的时候就被zynq的GPIO唬住了,实在没搞清楚zynq的GPIO怎么回事,一会这样,一会那..._zynq 的emio管脚在哪
文章浏览阅读782次。redis 7 安装 和 卸载_centos7卸载redis
Java中的for each循环可以用于遍历数组。通过for each循环,可以简洁地访问数组中的每个元素。本文介绍了如何使用for each循环遍历数组,并给出了相应的代码示例和输出结果。
文章浏览阅读454次。alter table table名auto_increment = 自增位置;_mysql 删除自增id mysql删除数据后id自动重置
文章浏览阅读728次,点赞21次,收藏9次。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。(1) and:逻辑与,在命令中用“-a”表示,是系统缺省的选项,表示只有当所给的条 件都满足时,寻找条件才算满足。-size n[bckw] 查找指定文件大小的文件,n 后面的字符表示单位,缺省为 b,代表512字节的块。-group ’字串’ 查找属于用户组名为所给字串的所有的文件。-user ’字串’ 查找属于用户名为所给字串的所有的文件。
文章浏览阅读126次。stm32时钟的基础知识
文章浏览阅读1.4w次,点赞3次,收藏14次。1.Intruction 在目标识别、对象追踪和图像分类等领域,对高质量合成孔径雷达图像(SAR image)的需求十分迫切。然而,合成孔径雷达图像的质量天生就会受到多重信道噪声的影响,这极大地阻碍了图像的应用。 2017年以来,基于深度学习的方法能很好地学习噪声图像和无噪声图像之间的底层映射,然而,与光学图像训练不同的是,在现实中无法直接获得无噪声的SAR图像。为了产生训练对,一..._适用于sar图像的去噪网络
文章浏览阅读108次。在头文件中要包含#import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename ("EOF","serEOF") //serEOF是自己随便起个名字,命名空间//插入函数① 与②不同是声明了_variant_t RecordsAffected;在生成字符串的时候用左右。//以SQ..._ado vc++ _connectionptr conn(__uuidof(connection))