COM组件设计与应用(十三)
事件和通知(VC6.0)

作者:杨老师

下载源代码

一、前言
  我的 COM 组件运行时产生一个窗口,当用户双击该窗口的时候,我需要通知调用者;
  我的 COM 组件用线程方式下载网络上的一个文件,当我完成任务后,需要通知调用者;
  我的 COM 组件完成一个钟表的功能,当预定时间到达的时候,我需要通知调用者;
  ... ... ... ...
  本回书开始话说 COM 的事件、通知、连接点......这些内容比较多,我分两次(共四回)来介绍。

二、通知的方法
  当程序甲方内部发生了某个事件的时候,需要通知乙方,无非使用几个方法:
 

通知方式 简单说明 评论
直接消息 PostMessage()
PostThreadMessage()
向窗口或线程发个消息 你什么时候执行我就不管啦
SendMessage() 马上执行消息响应函数 不执行完消息处理函数不会返回
SendMessage(WM_COPYDATA...) 发消息的同时,还可以带过去一些自定义的数据 比较常用,所以单独列了出来
间接消息 InvalidateRect()
SetTimer()
......
被调用的函数会发送相关的一些消息 这样的函数太多了
回调函数 GetOpenFileName()...... 当用户改变文件选择的时候,执行回调函数 嗨!哥们,这是我的电话,有事就言语一声。

  在 COM 的时代,以上这些方法就基本上不能玩转了,因为...您想呀 COM 组件是运行在分布式环境中的,地球另一边计算机上运行的组件,怎么可能给你的窗口发消息那?当然不能!(但话又说回来,对于 ActiveX 这样只能在本地运行的组件,当然也可以发送窗口消息的啦。)
  回调函数的方式,是设计 COM 通知方法的基础。回调函数,本质上是预先把某一函数的指针告诉我,当我有必要的时候,就直接呼叫该函数了,而这个回调函数做了什么,怎么做的,我是根本不关心的。好了,问你个问题:啥是 COM 的接口?接口其实就是一组相关函数的集合(这个定义不严谨,但你可以这么理解哈)。因此,在COM中不使用“回调函数”而是使用“回调接口”(说的再清楚一些,就是使用一大堆包装好的“回调函数”集) ,回调接口,我们也叫“接收器接口”。


图一、客户端传递接收器接口指针给COM。当发生事件时,COM调用接收器接口函数完成通知

本回示例程序完成的功能是:
  客户端启动组件(Simple11.IEvent1.1)并得到接口指针 IEvent1 *;
  调用接口方法 IEvent1::Advise() 把客户端内部的一个接收器(sink)接口指针(ICallBack *)传递到组件服务器中;
  调用 IEvent1::Add() 去计算两个整数的和;
  但是计算结果并不通过该函数返回,而是通过 ICallBack::Fire_Result() 返回给客户端;
  当客户端不再需要接受事件的时候,调用 IEvent1::Unadvise() 断开和组件的联系。

三、组件实现步骤
1、建立一个工作区(WorkSpace)
2、在工作区中,建立一个 ATL 工程(Project)。示例程序中工程名称叫 Simple11,接受全部默认选项。
3、ClassView 中,执行鼠标右键菜单命令 New Atl Object...,添加 ALT 类。
   3-1、左侧分类 Category 选择 Objects,右侧 Objects 选择 SimpleObject(其实就是默认项目)
   3-2、名称 Name 卡片中,输入组件名称。示例程序中是 Event1(注1)
   3-3、属性 Attributes 卡片中,修改接口类型 Interface 为定制的 Custom(注2)
4、ClassView 中,选择接口(IEvent1),鼠标右键菜单添加函数 Add Method...


图二、增加接口函数 Add([in] long n1,[in] long n2)


图三、增加接口函数 Advise([in] ICallBack *pCallBack,[out] long *pdwCookie)


图四、增加接口函数 Unadvise([in] long dwCookie)

  你应该注意到了,在Add()函数中,并没有[out]、[retval] 这样的 IDL 属性,嘿嘿,因为我们本来就不打算通过 Add() 函数直接得到计算结果。不然怎么演示回调接口呀:-) 另外,在函数 Advise()中,需要返回一个整数 dwCookie,这是干什么?道理很简单,因为我们的组件想同时支持多个对象的回调连接。因此当客户端传递一个接口给我们组件的时候,我返回给它唯一的一个 cookie 号码来表示身份,将来断开连接的时候 Unadvise(),它需要把这个 cookie 身份号再给我,这样我就知道是谁想断开了。
5、增加回调接口 ICallBack 的 IDL 定义。打开 IDL 文件并手工输入(黑体字部分为手工输入的) ,然后保存:

import "oaidl.idl";
import "ocidl.idl";
[object,uuid(7E659BB1-FB79-4188-9661-65CA22B6A3E6), // 这个 IID 可以用 GUDIGEN.EXE 产生helpstring("ICallBack Interface"),pointer_default(unique)
]
interface ICallBack : IUnknown
{};[object,     // 以下内容同示例程序,当然如果是你自己生成的程序就肯定有差别的啦uuid(7E659BB0-FB79-4188-9661-65CA22B6A3E6),helpstring("IEvent1 Interface"),pointer_default(unique)
]
interface IEvent1 : IUnknown
{[helpstring("method Add")] HRESULT Add([in] long n1, [in] long n2);[helpstring("method Advise")] HRESULT Advise([in] ICallBack * pCallBack, [out] long * pdwCookie);[helpstring("method Unadvise")] HRESULT Unadvise([in] long dwCookie);
};[uuid(695C9BB2-2AE9-4232-8225-17AB8BD3BABC),version(1.0),helpstring("Simple11 1.0 Type Library")
]
library SIMPLE11Lib
{importlib("stdole32.tlb");importlib("stdole2.tlb");[uuid(6FCF997C-C811-49DB-9D16-46FAF8D24822),helpstring("Event1 Class")]coclass Event1{[default] interface IEvent1;// 需要手工输入,据说 VB 使用的话,不能有 [source,default] 属性[source, default] interface ICallBack;    };
};

6、增加回调接口函数


图五、增加回调接口函数

其实和以前的方法一样,只要注意别选错了接口就好。


图六、增加接口函数 Fire_Result([in] long nResult)

我们计算整数和,得到结果后,就是要靠这个回调接口函数去反馈给客户端呀。

7、添加组件内部保存回调接口指针的数组
  刚才已经说过,我们这个组件打算支持多个对象的回调连接,因此我们要使用一个数组来保存。在 ClassView 中,选择 CEvent1 类,增加成员变量 Add Member Variable...


图七、增加保存 ICallBack * 的数组

  当然,保存一个数组可以有多种方式。示例程序比较简单,定义了一个10个元素空间的成员数组变量。如果你已经学会了使用 STL,那么你也可以用 vector 等容器来实现。注意!注意!注意!在构造函数中别忘了初始化数组元素为 NULL。

8、好了,下面开始完成所有代码

STDMETHODIMP CEvent1::Add(long n1, long n2)
{long nResult = n1 + n2;for( int i=0; i<10; i++){if( m_pCallBack[i] )  // 如果回调接口有效m_pCallBack[i]->Fire_Result( nResult );    // 则发出事件/通知}return S_OK;
}STDMETHODIMP CEvent1::Advise(ICallBack *pCallBack, long *pdwCookie)
{if( NULL == pCallBack )  // 居然给我一个空指针?!return E_INVALIDARG;for( int i=0; i<10; i++)  // 寻找一个保存该接口指针的位置{if( NULL == m_pCallBack[i] )    // 找到了{m_pCallBack[i] = pCallBack; // 保存到数组中m_pCallBack[i]->AddRef();   // 指针计数器 +1*pdwCookie = i + 1;   // cookie 就是数组下标// +1 的目的是避免使用0,因为0表示无效return S_OK;}}return E_OUTOFMEMORY;  // 超过10个连接,内存不够用啦
}STDMETHODIMP CEvent1::Unadvise(long dwCookie)
{if( dwCookie<1 || dwCookie>10 )  // 这是谁干的呀?乱给参数return E_INVALIDARG;if( NULL == m_pCallBack[ dwCookie - 1 ] )    // 参数错误,或该接口指针已经无效了return E_INVALIDARG;m_pCallBack[ dwCookie -1 ]->Release(); // 指针计数器 -1m_pCallBack[ dwCookie -1 ] = NULL;      // 空出该下标的数组元素return S_OK;
}

四、客户端实现步骤
  大家下载示例程序后,去浏览客户端的实现程序吧。这里我只说明一下关于接收器是如何构造的:


图八、从 ICallBack 派生接收器类 CSink

  从 ICallBack 派生一个类 CSink。确认后 IDE 会有一个警告,说它找不到 ICallBack 的头文件,不用理它,因为只有当编译的时候,#import 才会为我们生成 xxxx.tlh、xxxx.tli 文件,这些文件就有 ICallBack 的声明啦。
  这里 ICallBack 是 COM 接口,因此 CSink 是不能事例化的,如果你去编译,会得到一坨一坨(注3)的错误,报告说你没有实现 virtual 函数。然后,我们可以按照错误报告,去实现所有的虚函数:

// STDMETHODIMP 是宏,等价于 long __stdcall
STDMETHODIMP CSink::QueryInterface(const struct _GUID &iid,void ** ppv)
{*ppv=this;    // 不管想得到什么接口,其实都是对象本身return S_OK;
}ULONG __stdcall CSink::AddRef(void)
{   return 1;   }// 做个假的就可以,因为反正这个对象在程序结束前是不会退出的ULONG __stdcall CSink::Release(void)
{   return 0;   }// 做个假的就可以,因为反正这个对象在程序结束前是不会退出的STDMETHODIMP CSink::raw_Fire_Result(long nResult)
{... ...    // 把计算结果显示在窗口中return S_OK;
}

五、小结
  COM 组件实现事件、通知这样的功能有两个基本方法。今天介绍的回调接口方式非常好,速度快、结构清晰、实现也不复杂;下回书介绍连接点方式(Support Connection Points),连接点方法其实并不太好,速度慢(如果是远程DCOM方式,要谨慎选择它)、结构复杂、唯一的好处就是 ATL 对它进行了包装,所以实现起来反而比较简单。不介绍又不行,因为微软绝大数支持事件的组件都是用连接点实现的,咳......讨厌的微软(注4)。


注1:本来设想多举几个例子,因此第一个叫 Event1,可写完后,感觉程序已经比较复杂了,就没继续再做了。
注2:当然,你选择使用双接口 Dual 也没有问题。但要注意到在下面的步骤,增加回调接口修改 IDL 文件的时候,我们是要使用 Custom(从IUnknown派生,而不是从IDispatch派生)的。
注3:一坨一坨经常用来形容一堆一堆的狗屎。
注4:微软的同志们,玩笑话不要当真呀!我还靠着你来吃饭那。

转载于:https://www.cnblogs.com/duzouzhe/archive/2009/07/22/1528899.html

COM组件设计与应用(十三)(转载)相关推荐

  1. 【转载】COM 组件设计与应用(四)——简单调用组件

    原文:http://vckbase.com/index.php/wv/1211.html 一.前言 同志们.朋友们.各位领导,大家好. VCKBASE 不得了, 网友众多文章好. 组件设计怎么学? 知 ...

  2. 【转载】COM 组件设计与应用(二)——GUID 和 接口

    原文:http://vckbase.com/index.php/wv/1203.html COM 组件设计与应用 系列文章:http://vckbase.com/index.php/piwz?& ...

  3. COM组件设计与应用(三)(转载)

    COM组件设计与应用(三) 数据类型 作者:杨老师 一.前言 上回书介绍了GUID.CLSID.IID和接口的概念.本回的重点是介绍 COM 中的数据类型.咋还不介绍组件程序的设计步骤呀?咳..... ...

  4. COM 组件设计与应用(六)——用 ATL 写第一个组件(vc.net)

    一.前言 1.与 <COM 组件设计与应用(五)>的内容基本一致.但本回讲解的是在 vc.net 2003 下的使用方法,即使你不再使用vc6.0,也请和上一回的内容,参照比对. 2.这第 ...

  5. 用 C# 做组件设计时的事件实现方法讨论

    事件,其实就是将物体的某个过程处理通过委托(delegate, 也就是函数指针) 的方式公开给外部的自定义函数处理. C# 可以使用多播委托,但实际上一般情况下只需要用到单播. 事件需要通过调用到那个 ...

  6. [译] 前端组件设计原则

    原文地址:Front end component design principles 原文作者:Andrew Dinihan 文中示例代码:传送门 限于个人能力,如有错漏之处,烦请不吝赐教. 前言 我 ...

  7. COM 组件设计与应用(六)

    一.前言 1.与 <COM 组件设计与应用(五)>的内容基本一致.但本回讲解的是在 vc.net 2003 下的使用方法,即使你不再使用vc6.0,也请和上一回的内容,参照比对. 2.这第 ...

  8. COM 组件设计与应用(七)

    COM 组件设计与应用(七) 编译.注册.调用 作者:杨老师 一.前言 上两回中,咱们用 ATL 写了第一个 COM 组件程序,这回中,主要介绍编译.注册和调用方法.示例程序你已经下载了吗?如果还没有 ...

  9. COM 组件设计与应用(十一)

    COM 组件设计与应用(十一) IDispatch 及双接口的调用 作者:杨老师 下载源代码 一.前言     前段时间,由于工作比较忙,没有能及时地写作.其间收到了很多网友的来信询问和鼓励,在此一并 ...

最新文章

  1. php 桥接 微信80端口,解决MAC系统在做微信开发时候tomcat无法使用80端口问题
  2. Win11系统显示你的账户已被停用怎么办
  3. JAVA语法——经典题目02
  4. 终于找到原因!大厂面试被拒,是你不会数据结构
  5. 同步fifo的串并_同步FIFO笔记
  6. JavaScript中document.getElementById和document.write
  7. latex生成pdf中文标签乱码pdf复制乱码
  8. SU插件|实时联动Lumion LiveSync for SketchUp免费下载(渲染器与草图大师模型同步更新)
  9. 手机上如何修改寸照背景颜色
  10. 快递系统java实验报告_java模拟物流快递系统程序.doc
  11. 阻止野猪入境 丹麦在德国边境开始建设边境围栏
  12. Java基础篇--集合(map)
  13. 低合金Q355D材料中的机械性能名称
  14. 【微机原理】数字电路器件—门 与门 或门 非门电路及实例
  15. 自媒体人必备16种工具大全,这些工具你值得拥有干货
  16. 【文献调研】三相DLMP的motivation调研
  17. 麦克风声源定位原理_使用麦克风阵列对声源定位的方法
  18. shapenet数据集_三维形状数据的深度特征表示
  19. 5.6-5.8工作记录2—分页模糊查询
  20. PCA降维算法(内含PCA可视化迷你案例+PCA人脸识别降维案例+PCA逆转降噪案例)

热门文章

  1. axure 素材_Axure原型:超漂亮的系统首页
  2. excel统计行数_工程人常用的12个excel和9个wps技巧
  3. linux mysql 文件恢复_linux下误删数据文件恢复
  4. laravel increment出现了翻倍递增_中国股市:如何判断“强庄股”的出现,看懂主力心甘情愿送钱上门...
  5. 这个陶瓷电阻烙铁架不错哦,最新一期的电子趣事分享给大家
  6. 嵌入式的坑在哪方面?
  7. pregquote php,PHP: preg_quote - Manual
  8. oracle 长事务 逻辑日志,goldengate中长事务引起的问题
  9. mysql主从配置 51cto_Mysql主从复制配置
  10. java嵌入groovy脚本_Java封装groovy脚本引擎并支持脚本包含(include)功能