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

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

 

通知方式简单说明评论
直接消息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:微软的同志们,玩笑话不要当真呀!我还靠着你来吃饭那。
下载源代码

一、前言

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

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

相关推荐

Windows 11 系统部署实战:3 步完成 VC++、.NET、DirectX 运行库自动化安装

本文详细介绍了在Windows 11系统中通过PowerShell脚本实现VC++、.NETDirectX运行库的自动化安装方案。针对IT管理员系统工程师,提供三步部署流程,包括环境检测、静默安装验证机制,显著提升运行库部署效率,解决版本碎片化依赖复杂等问题。

weixin_33700350的博客 1121

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

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

解决VS2019中COM控件“已添加但未启用”的32/64位兼容性问题

在Windows软件开发中,COM组件对象模型)技术曾是实现二进制代码复用的核心机制,它允许不同语言编写的模块相互调用。其原理基于注册表接口约定,实现进程间通信对象创建。这项技术的价值在于它支撑了大量遗留系统行业软件(如工控、CAD)的组件化架构。然而,在64位系统普及后,32COM组件64位开发环境的兼容性问题日益凸显,典型场景便是在Visual Studio 2019等现代IDE中集成旧版ActiveX控件时,常遇到控件在工具箱中可见却无法在设计器使用的困境。本文聚焦这一高频痛点,深入剖析其

weixin_30096975的博客 277

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

matlab下做com组件

邓科峰 dkf03@mails.tsinghua.edu.cn                                           matlab的combuilder系列-matlab下做com组件 com builder是matlab6.5才有的,也是mathworks公司推荐使用于混合编程的,这些日子我对他进行了全方位的摸索,感觉是爽呆了,下面我们一起来揭开它的神秘面纱,^_

§为艺术而技术§ 8698

COM组件在实现VB.net 调用MATLAB中的应用

第一篇博文,有空在放图! 1. 通过MATLAB引擎(Engine)---接口函数 提供了一组Mat lab API函数,用户不必关心MATLAB引擎是如何实现的,只要调用这些函数即可。正是通过这些API函数实现应用程序进程之间的传递函数,从而实现Mat labVC的混合编程。VC的C语言或C++语言向Mat lab引擎传递命令数据信息,通过调用MATLAB引擎在后台...

weixin_30681615的博客 1119

VC++调用VB.NET缩写的COM

现在COM组件的使用越来越广泛,在各个程序中调用COM组件我想大家都遇到过,这篇文章就是关于COM组件的编写调用的,主要包含了使用VC6.0编写调用COM组件,VS2005中使用VB编写调用COM组件,以及在VC6.0VS2005之间互相调用COM组件。操作规程上一篇C

jeromeyu的专栏 1555

vc中调用Com组件的方法详解

转载自:网络,来源未知,如有知晓者请告知我。需求:1.创建myCom.dll,该COM只有一个组件,两个接口: IGetRes--方法Hello(), IGetResEx--方法HelloEx()2.在工程中导入组件或类型库 #import "组件所在目录myCom.dll" no_namespace 或 #import "类型库所在目录myCom.tlb"u...

amx96815的博客 128

总结:com组件 vsto 的参考质料

编写可复用性更好的C++代码http://www.vckbase.com/document/viewdoc/?id=457用VC进行COM编程,必须要掌握哪些COM理论知识http://www.donews.net/Grandia/archive/2005/02/07/270979.aspxBSTR的使用(COM中的string)http://blog.csdn.net/pkr

958

.NET 10 AOT编绎成DLL调用方式-Activex dll/标准DLL

摘要:.NET 10 AOT编译后生成的二进制文件无法直接被非AOT项目调用,需采用特定方案:1) VC导出函数模式可行但需手动封装对象内存管理;2) ActiveX DLL方案已验证可行,支持面向对象调用但仅限Windows平台;3) 普通.NET DLL方式不可行,因缺少元数据。建议根据平台需求选择:跨平台用模式1,Windows可视化用模式2,或通过拆分项目共享逻辑。关键验证显示ActiveX方案已成功用于VB6.NET Framework项目调用。

1106

C#创建COM组件

http://www.cnblogs.com/panlijiao/archive/2012/10/14/2773882.html 用C##创建COM组件看来是个很简单的功能,但因为此类文章较少,还是有很多不明白的地方需要学习。本文详细阐述如何用C#创建COM组件,并能用VC6.0等调用。   本文详细阐述如何用C#创建COM组件,并能用VC6.0等调用。并附有完整测试通过的代码。该功能总

hemeinvyiqiluoben的专栏 632

PDB版本兼容性详解:从VC2VC140的格式演进变化

**PDB版本兼容性**是Windows平台调试符号处理中的核心议题。作为Microsoft的Program Database格式,PDB文件承载了从VC2VC140等多个Visual Studio版本的调试信息。了解PDB格式的演进历程对于开发者处理跨版本调试、符号服务器管理工具链集成至关重要。本文将深入解析PDB格式从早期版本到现代版本的完整演进路径,帮助您掌握PDB版本兼容性的关键要点

gitblog_00523的博客 961

Windows安装错误0x80070643:从系统组件到安装机制的深度修复指南

在Windows系统软件部署过程中,安装失败是常见的运维挑战,其背后往往涉及系统组件、服务状态及依赖关系的复杂交互。Windows Installer作为核心安装引擎,其服务健康度、缓存完整性以及系统文件的一致性直接决定了软件安装的成功率。从技术价值看,掌握系统级故障排查方法不仅能解决特定错误代码(如0x80070643),更能提升对Windows软件生态链的底层认知,增强系统稳定性维护能力。典型的应用场景包括企业软件批量部署、遗留系统兼容性维护以及开发环境配置等。本文聚焦于Windows Live Ess

weixin_30536513的博客 615

InnoDependencyInstaller:彻底解放Windows应用安装!自动下载.NET/VC++/SQL等60+依赖的终极方案

InnoDependencyInstaller是一款基于Inno Setup的强大依赖安装工具,能够在Windows应用安装过程中自动下载并安装.NET、Visual C++、SQL Server、WebView2等60多种常用依赖组件。只需一行代码即可集成,自动检测系统缺失组件并优先安装,已存在的依赖则智能跳过,大幅提升应用部署效率用户体验。 ## 为什么选择InnoDependencyIn

gitblog_00423的博客 375

InnoDependencyInstaller支持的60+依赖清单:.NET/VC++/SQL/Java全收录

InnoDependencyInstaller是一款强大的Inno Setup脚本工具,能够在应用程序安装过程中自动下载并安装各种依赖项,如.NET、Visual C++、SQL ServerWebView2等。它采用智能检测机制,仅安装缺失的依赖,已存在的则自动跳过,极大简化了应用部署流程。目前已内置超过60种常用依赖,涵盖开发环境、运行时库数据库等多种类型。 ## 🚀 核心依赖类别概览

gitblog_00084的博客 256
上一篇: COM组件设计与应用13 - 事件和通知(VC6.0)
下一篇: COM组件设计与应用15 - 连接点(vc6.0)
snaill
博客等级 码龄27年 599粉丝 159原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值