COM组件设计与应用(十四)事件和通知(vc.net)

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

http://www.vckbase.com/code/downcode.asp?id=2753 下载源码

一、前言   我的 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、建立一个解决方案 2、在解决方案中,建立一个 ATL 项目。示例程序中项目名称叫 Simple12,取消“属性化”,其它接受默认选项。 3、选择项目,执行鼠标右键菜单命令“添加/添加类”。     3-1、左侧分类选择 ATL,右侧模板选择 Atl 简单对象     3-2、名称卡片中,输入组件名称。示例程序中是 Event1(注1)     3-3、选项卡片中,修改接口类型“自定义”(注2) 4、选择 IEnvent1 接口,鼠标右键菜单“添加/添加方法” 图二、增加接口函数 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(DB72DF86-70E9-4ABC-B2F8-5E04062D3B2E), // 这个 IID 可以用 GUDIGEN.EXE 产生
 helpstring("ICallBack 接口"),
 pointer_default(unique)
]
interface ICallBack : IUnknown
{

};

[
 object,  // 以下内容同示例程序,当然如果是你自己生成的程序就肯定有差别的啦
 uuid(DB72DF85-70E9-4ABC-B2F8-5E04062D3B2E),
 
 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(FBA1E0F0-49CD-4B77-B9B1-4DC066AF8A8E),
 version(1.0),
 helpstring("Simple12 1.0 类型库")
]
library SIMPLE11Lib
{
 importlib("stdole32.tlb");
 importlib("stdole2.tlb");

 [
  uuid(53E00126-B1A0-4510-B9BC-75ED87CE2DB7),
  helpstring("Event1 Class")
 ]
 coclass Event1
 {
  [default] interface IEvent1;
  // 需要手工输入,据说 VB 使用的话,不能有 [source,default] 属性
  [source, default] interface ICallBack; 
 };
};

6、增加回调接口函数 图五、增加回调接口函数   其实和以前的方法一样,只要注意别选错了接口就好。 图六、增加接口函数 Fire_Result([in] long nResult)   我们计算整数和,得到结果后,就是要靠这个回调接口函数去反馈给客户端呀。 7、添加组件内部保存回调接口指针的数组   刚才已经说过,我们这个组件打算支持多个对象的回调连接,因此我们要使用一个数组来保存。由于 vc.net 无法用向导来添加数组形式的成员变量,我们还是打开 CEvent1 类的头文件,手工输入吧:

......
private:
 ICallBack * m_pCallBack[10];
......

  保存一个数组可以有多种方式。示例程序比较简单,定义了一个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 是 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:微软的同志们,玩笑话不要当真呀!我还靠着你来吃饭那。

OLE技术专题——第四讲:COM的连接点事件(上) 作者:杨老师一、前言  我的 COM 组件运行时产生一个窗口,当用户双击该窗口的时候,我需要通知调用者;  我的 COM 组件用线程方式下载网络上的一个文件,当我完成任务后,需要通知调用者;  我的 COM 组件完成一个钟表的功能,当预定时间到达的时候,我需要通知调用者;  ... ... ... ...  本回书开始话说 COM事件通知、连接点......这些内容比较多,我分两次(共四回) 阅读详情

相关推荐

VC6用C++调用VS中C#生成DLL/COM的方法注意事项

先感慨一下,研究这个问题研究了四个工作日才解决,实在是太不容易了,现在很开心很得意,叉会腰哈哈~ 虽然就是一件看起来很简单的事情,我相信你在看到我这篇文章之前也已经看过许多相关文章了,但是很多细节需要注意,哪一步没有做好可能就走入了一个无尽的陷阱绕许多弯路,把我的经验分享出来如果能帮助到也遇到相同问题的小伙伴,就真的太好不过了。(我的开发环境:WinXP-VC++6.0 C++ &amp; Wi...

一颗Static逃 2332

VC++ COM组件回调(EXE组件回调客户端)

VC++ COM组件回调客户端(EXE组件回调客户端,服务端非DLL形式),通过客户端触发调用服务端,服务端回调客户端,IDE为VS2013 update5

杨老师深入解析COM组件设计应用(附源码)

ATL(Active Template Library)是微软推出的一个用于简化COM组件开发的C++模板库。它提供了一组用于创建轻量级、高效组件的工具类。ATL组件是一种基于COM的二进制接口标准,使得开发者可以快速构建符合工业标准的组件,而无需从头开始编写大量的底层代码。ATL通过模板少量宏来减少开发工作量,它提供了一系列的自动化特性,例如自动处理COM对象的注册注销、实现接口以及处理IDispatch接口等。

weixin_35459464的博客 985

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

COM组件设计应用(十三) 事件通知(VC6.0) 作者:杨老师 下载源代码 一、前言   我的 COM 组件运行时产生一个窗口,当用户双击该窗口的时候,我需要通知调用者;   我的 COM 组件用线程方式下载网络上的一个文件,当我完成任务后,需要通知调用者;   我的 COM

snail8384的专栏 781

COM连接点 - 最简单的例子 (1)

先创建一个COM接口,IMyCar

zj510的专栏 1万+

COM 连接点事件讲解

开发环境:VC++6.0测试环境:windows 2000 这篇文章是Alex C. Punnen.所写,在http://www.codeguru.com/上发表的,在此本人将其翻译成中文,大家共享。 (注:本人翻译不当之处,还请各位多指正谅解)运行环境:Windows 2000 Server, Microsoft Visual C++ 6此篇文章将用一个清晰的例子来描述COM中连

步入什么 3923

COM连接点事件event

COM 中的典型方案是让客户端对象实例化服务器对象,然后调用这些对象。然而,没有一种特殊机制的话,这些服务器对象将很难转向并回调到客户端对象。COM 连接点便提供了这种特殊机制,实现了服务器客户端之间的双向通信。使用连接点,服务器能够在服务器上发生某些事件时调用客户端。 有了连接点,服务器可通过定义一个接口来指定它能够引发的事件。服务器上引发事件时,要采取操作的客户端会向服务器

丸子头的专栏 4623

COM组件设计应用十四)---事件通知(vc.net

COM组件设计应用十四事件通知(vc.net) 作者:杨老师 下载源代码 一、前言   我的 COM 组件运行时产生一个窗口,当用户双击该窗口的时候,我需要通知调用者;   我的 COM 组件用线程方式下载网络上的一个文件,当我完成任务后,需要通知调用者;   我的 COM

snail8384的专栏 631

COM 组件设计应用十四)——事件通知(vc.net)

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

junchieh 566

COM组件设计应用十四)——事件通知(vc.net)

 COM组件设计应用十四)——事件通知(vc.net) [ 录入者:it3650 | 时间:2006-09-09 19:47:52 | 作者:杨老师 |   来源:VC知识库 ]

549

【转载】COM 组件设计应用十四)——事件通知(vc.net)

原文:http://vckbase.com/index.php/wv/1244.html 一、前言 我的 COM 组件运行时产生一个窗口,当用户双击该窗口的时候,我需要通知调用者; 我的 COM 组件用线程方式下载网络上的一个文件,当我完成任务后,需要通知调用者; 我的 COM 组件完成一个钟表的功能,当预定时间到达的时候,我需要通知调用者; ... ... ... ... 本回...

weixin_30323631的博客 169

COM组件设计应用十四)--事件通知(vc.net)

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

Welcom to zougangx's blog 423

COM 组件设计应用ALL

COM 组件设计应用(十八)——属性包 COM 组件设计应用(十七)——持续性 COM 组件设计应用(十六)——事件通知(vc.net) COM 组件设计应用(十五)——事件通知(VC6.0) COM 组件设计应用十四)——事件通知(vc.net) COM 组件设计应用(十三)——事件通知(VC6.0) COM 组件设计应用(十二)——错误异常处理

swifty的专栏 1043

COM 组件设计应用——杨老师专辑

COM 组件设计应用(十八)——属性包COM 组件设计应用(十七)——持续性COM 组件设计应用(十六)——连接点(vc.net)COM 组件设计应用(十五)——连接点(VC6.0)COM 组件设计应用十四)——事件通知(vc.net):本回示例程序完成的功能是: 客户端启动组件(Simple11.IEvent1.1)并得到接口指针 IEvent1 *; 调用

jiht594的专栏 1961

COM组件设计应用14 - 事件通知(vc.net)

  通知方式 简单说明 评论 直接消息 PostMessage()PostThreadMessage() 向窗口或线程发个消息 你什么

蜗牛档案室 1355

COM组件设计应用(十六)---连接点(vc.net)

COM组件设计应用(十六) 连接点(vc.net) 作者:杨老师 下载源代码 一、前言   上回书介绍了回调接口,在此基础上,我们理解连接点就容易多了。 二、原理 图一、连接点组件原理图。

snail8384的专栏 626

COM组件设计应用10 - IDispatch 接口 for vc.net

下载源代码 一、前言    终于写到了第十回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用、非常有用、非常精彩的一个 COM 功能。由于 WORD、EXCEL 等 OFFICE 软件提供了“宏”的功能,就连我们使用的VC开发环境也提供了“宏”功能,更由于 HTML、ASP、JSP 等都要依靠脚本(Script)的支持,更体现出了自动化接口的重要性。   

蜗牛档案室 1611

COM组件设计应用(十六)

 COM组件设计应用(十六)连接点(vc.net)作者:杨老师下载源代码一、前言  上回书介绍了回调接口,在此基础上,我们理解连接点就容易多了。二、原理图一、连接点组件原理图。左侧为客户端,右侧为服务端(组件对象)  看着好复杂呀......呵呵,其实简单的紧:(注1)1、一个 COM 组件,允许有多个连接点对象(IConnectionPoint)。   也就是说可以有多个发生“事件”的

bird207的专栏 341
上一篇: COM组件设计与应用(十三)事件和通知(VC6.0)
下一篇: COM组件设计与应用(十五)连接点(vc6.0)
hejinjiang
博客等级 码龄19年 31粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值