|
一、前言
我的 COM 组件运行时产生一个窗口,当用户双击该窗口的时候,我需要通知调用者; 我的 COM 组件用线程方式下载网络上的一个文件,当我完成任务后,需要通知调用者; 我的 COM 组件完成一个钟表的功能,当预定时间到达的时候,我需要通知调用者; ... ... ... ... 本回书开始话说 COM 的事件、通知、连接点......这些内容比较多,我分两次(共四回)来介绍。 二、通知的方法 当程序甲方内部发生了某个事件的时候,需要通知乙方,无非使用几个方法:
在 COM 的时代,以上这些方法就基本上不能玩转了,因为...您想呀 COM 组件是运行在分布式环境中的,地球另一边计算机上运行的组件,怎么可能给你的窗口发消息那?当然不能!(但话又说回来,对于 ActiveX 这样只能在本地运行的组件,当然也可以发送窗口消息的啦。) 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 对它进行了包装,所以实现起来反而比较简单。不介绍又不行,因为微软绝大数支持事件的组件都是用连接点实现的,咳......讨厌的微软 | ||||||||||||||||||||||
OLE技术专题——COM的连接点事件(上)(转)
最新推荐文章于 2024-09-24 10:12:14 发布
本文详细介绍了COM组件如何通过回调接口实现事件通知功能,包括组件的设计思路、具体实现步骤及客户端如何对接收器进行构造。








3218

被折叠的 条评论
为什么被折叠?



