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

  
  •  
  •  
  •          
  •  
    • 更多精彩VC知识,请关注微博:
    当前位置: 首页 >  文章列表 > COM组件设计与应用(十三)——事件和通知(VC6.0)



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

    发布日期:
    2005-09-01 10:36
    浏览次数:
    5236次
    标  签:
    COM
    文章评分:
    • 5.0
    操  作:
    下载源文件 打印
    杨老师
    称号:未设置
    简介:...
    • 文章概要:
    • 我的 COM 组件运行时产生一个窗口,当用户双击该窗口的时候,我需要通知调用者; 我的 COM 组件用线程方式下载网络上的一个文件,当我完成任务后,需要通知调用者; 我的 COM 组件完成一个钟表的功能,当预定时间到达的时候,我需要通知调用者;

    一、前言

    我的 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();	// 指针计数器 -1
    	m_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:微软的同志们,玩笑话不要当真呀!我还靠着你来吃饭那。 

    评论 1个评论
    haojg2008 评论道:

    不错

    • 2012-12-11 13:47 发表
    • 引用
    发表评论
    • 最多还可以输入100
    最新动态 -

     【牛人都在千人一号群! 加群三步走!!!】
    第一步:请必须加VC知识库QQ:2334274564 为好友;
    第二步:请必须关注本站微博: ;
    第三步:申请加入群:312987540.(必须将关注微博截屏发到QQ方可通过!)

     VIP套餐优惠活动!!!
    最新活动:6月28日 - 7月15日 VIP套餐优惠价:1500。欲购从速!
    本群(群号:286712886)为团购群,7.5晚上20:00,本群的群友一起购买VIP套餐,即可实现团购。人越多价格越便宜。

     【UIPower徐州分公司招聘C/C++软件工程师】
    UIPower徐州分公司 研发部招聘C/C++软件工程师。 有意请发简历到HR邮箱:hr@uipower.com

     【2013/7/1 新视频发布】 
    1、《文件打包技术详解》第三讲:资源的加密. 时长:54分钟,主讲人:步磊峰.【38元】 
    2、《文件打包技术详解》第四讲:文件压缩与完整性检测. 时长:36分钟,主讲人:步磊峰.【28元】 

     【C/C++软件工程师实战能力集训大纲】
    2013-4-26 VC知识库发布了C/C++业界的“本草纲目”《C/C++软件工程师实战能力集训大纲》详细介绍 作者:VC知识库

     【最新文章发布】
    2013-4-20 发布《Windows Shell扩展(Shell Extensions)指南》,作者:scorpio172

     【VC知识库•网校】
    本站在传课网上的网校地址:http://www.chuanke.com/s1265063.html

     【VIP年会员制套餐】 
    只要具有C/C++语法基础,就可以在短短几个月内迅速打造成为C/C++技术牛人!本系列课程的主讲人都是知名产品的创始人,实力雄厚,培训效果立竿见影。学完后无需我们推荐工作,您自己就可以轻松找到好工作!
     

    文章排行
    在工业控制中,工控机(一般都基于Windows平台)经常需要与智能仪表通过串口进行通信。串口通信方便易行,应用广泛。   一般情况下,工控机和各智能仪表通过RS485总线进行通信。RS485的通信方式是半双工的,只能由作为主节点的工控PC机依次轮询网络上的各智能控制单元子节点。每次通信都是由PC机通过串口向智能控制单元发布命令,智能控制单元在接收到正确的命令后作出应答。... 查看全文>>
    多线程编程之二——MFC中的多线程开发
    代码注入的三种方法
    改变 CListCtrl、CHeaderCtrl 高度、字体、颜色和背景
    初识WTL(上)
    用 VC++建立 Windows 服务程序
    VC6中使用CHtmlView在对话框控制中显示HTML文件
    COM 组件设计与应用(一)——起源及复合文件
    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值