在最近的一篇文章中说到了,如何创建ActiveX,这次我们来响应事件。这次,我们将创建一个类:CGeneralEventSink,它能够响应任何Dispatch事件(事件的接口继承与IDispatch)。

首先,我们来回顾一下ConnectionPoint的概念。任何支持事件的对象(比如,ActiveX控件),都支持IConnectionPointContainer接口,顾名思义就是一个IConnectionPoint的容器,包含了这个对象支持的全部事件。IConnectionPoint代表了一组事件,调用IConnectionPoint::Advise并传入我们想要接收事件的对象指针。而IConnectionPoint::GetConnectionInterface返回的IID,是此接收事件的对象必须实现的接口,否则Advise会失败。来看一下AxAdviseAll的代码:

//枚举IConnectionPointContainer中的每个IConnectionPoint,
//对于每个IConnectionPoint新建一个支持iid接口的CGeneralEventSink对象,并Advise。
//pUnk是控件的指针
HRESULT AxAdviseAll(IUnknown * pUnk)
{
HRESULT hr;
IConnectionPointContainer * pContainer = NULL;
IConnectionPoint * pConnectionPoint=NULL;
IEnumConnectionPoints * pEnum = NULL;
hr = pUnk->QueryInterface(IID_IConnectionPointContainer,(void**)&pContainer);
if (FAILED(hr)) goto error1;
hr = pContainer->EnumConnectionPoints(&pEnum);
if (FAILED(hr)) goto error1;
ULONG uFetched;
while(S_OK == (pEnum->Next(1,&pConnectionPoint,&uFetched)) && uFetched>=1)
{
DWORD dwCookie;
IID iid;
hr = pConnectionPoint->GetConnectionInterface(&iid);
if (FAILED(hr)) iid = IID_NULL;
//这里传入pUnk是为了通过pUnk得到iid的ITypeInfo,进而判断iid是否是Dispatch接口
IUnknown * pSink = new CGeneralEventSink(iid,pUnk);
hr = pConnectionPoint->Advise(pSink,&dwCookie);
pSink->Release();
pConnectionPoint->Release();
pConnectionPoint = NULL;
pSink = NULL;
}
hr = S_OK;
error1:
if (pEnum)pEnum->Release();
if (pContainer) pContainer->Release();
if (pConnectionPoint) pConnectionPoint->Release();
return hr;
}

然后,来说一下Dispath事件。如果IConnectionPoint::GetConnectionInterface返回的IID代表的接口是继承于IDispatch的话,这个事件就是一个Dispath事件。当事件发生时,产生事件的对象不会直接调用pObj->OnEvent(),而会调用pObj->Invoke(…)。例如,Flash控件的事件对象:(通过vc的#import指令生成的)

struct __declspec(uuid("d27cdb6d-ae6d-11cf-96b8-444553540000"))
_IShockwaveFlashEvents : IDispatch
{
//
// Wrapper methods for error-handling
// // Methods:
HRESULT OnReadyStateChange (
long newState );
HRESULT OnProgress (
long percentDone );
HRESULT FSCommand (
_bstr_t command,
_bstr_t args );
HRESULT FlashCall (
_bstr_t request );
};

当Flash控件产生事件时,它会调用IDispath::Invoke而不是,OnReadyStateChange等方法。只有在vc,atl等框架里面,这些框架会自动生成Invoke函数,在Invoke函数中根据参数的不同(第一个参数memid代表代表哪个方法被调用),再调用OnReadyStateChange等方法。

所以,虽然IConnectionPoint要求实现的接口是_IShockwaveFlashEvents,但是我们的虚表中,只要存在IDispath的方法即可,虚表中之后_IShockwaveFlashEvents的方法不会被直接调用(如果我们自己实现Invoke,也不会去调用的)。我们告诉Flash控件,这是一个_IShockwaveFlashEvents接口,虽然它只实现了IDispath。就像CGeneralEventSink中的代码:

STDMETHOD(QueryInterface(REFIID riid,void **ppvObject))
{
*ppvObject = NULL;
if ( IID_IUnknown == riid)
{
*ppvObject = (IUnknown*)this;
}
else if (IID_IDispatch == riid || m_iid == riid)
{
*ppvObject = (IDispatch*)this;
}
else
{
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}

其中m_iid是IConnectionPoint要求实现的接口,通过CGeneralEventSink构造函数参数传入的。当然,m_iid一定要是一个dispatch接口(继承于IDispatch)。如果IConnectionPoint要求的接口不是继承于IDispatch的,则m_iid会被设置为IID_NULL,然后IConnectionPoint便得不到所要求的接口,Advise就会失败,否则的话控件就会调用一个虚表中不存在的方法(不是IDispatch事件的话,控件只能直接调用接口的方法,而不是Invoke),产生错误。

那么,如何判断m_iid是Dispatch接口呢?首先,得到代表此接口的ITypeInfo指针,这个请参考代码中的:

HRESULT CGeneralEventSink::GetIIDTypeInfo(IID iid,ITypeInfo ** ppInfo,IUnknown * pRelateObj);

简单的说,就是控件的接口pRelateObj会实现IProvideClassInfo接口,来提供它本身的ITypeInfo。再通过ITypeInfo::GetRefTypeInfo可以得到相关事件的ITypeInfo。

接着,此ITypeInfo的TYPEATTR结构中的typekind表明了,此ITypeInfo是否的Dispatch接口,代码如下:

TYPEATTR *attr;
if (SUCCEEDED(pInfo->GetTypeAttr(&attr)))
{
if (attr->typekind == TKIND_DISPATCH) isDispatch = true;
pInfo->ReleaseTypeAttr(attr);
}

最后,CGeneralEventSink的IDispatch方法全部返回E_NOTIMPLE就可以了,毕竟控件只是通知我们事件发生了,而不关心我们有什么反应。当然,为了让提供的示例更有趣,代码里面的Invoke做了详细的log(在得到接口的ITypeInfo的同时,也枚举了MEMID/DISPID对应的名字。还从注册表中把iid变成了接口的名字,所有这一切参考CGeneralEventSink的构造函数)。

代码下载

Win32编程点滴5 - 响应ActiveX控件的事件的更多相关文章

  1. Win32编程点滴3 - 简单ActiveX控件的使用

    虽然这里一片的.net气氛,到处充斥着像MVC.WPF.WorkFlow.LINQ等各种niubility的术语.但我们使用的Windows还是由COM技术主宰着:我们在选择日常使用的软件时,也会避免 ...

  2. 如何给ActiveX控件添加“事件”“属性”“标准事件”“自定义事件”等一些相关操作

    上一篇小编带大家熟悉了一下ActiveX的建立以及相关的概念,(http://blog.csdn.net/u014028070/article/details/38424611) 本文介绍下如何给控件 ...

  3. c# 开发ActiveX控件,添加事件,QT调用事件

    c# 开发 ActiveX 的过程参考我的另一篇文章 :  https://www.cnblogs.com/baqifanye/p/10414004.html 本篇讲如何 在C# 开发的ActiveX ...

  4. 用ATL和MFC来创建ActiveX控件

    摘要:目前MFC和ATL代表了两种框架,分别面向不同类型的基于Windows的开发.MFC代表了创建独立的Windows应用的一种简单.一致的方法:ATL提供了一种框架来实现创建COM客户机和服务器所 ...

  5. MFC-[转]基于MFC的ActiveX控件开发

    作者:lidan | 出处:博客园 | 2012/3/13 16:10:34 | 阅读22次 ActiveX 控件是基于组件对象模型 (COM) 的可重用软件组件,广泛应用于桌面及Web应用中.在VC ...

  6. 【转载】基于MFC的ActiveX控件开发(3)

    原文:http://iysm.net/?p=122 3.事件 ActiveX 控件使用事件通知容器控件上发生了某些事情.事件的常见示例包括单击控件.使用键盘输入数据和控件状态更改.当发生这些操作时,控 ...

  7. 以编程方式使用 Microsoft Office Visio 2003 ActiveX 控件

    以编程方式使用 Microsoft Office Visio 2003 ActiveX 控件 2007/10/29 Mark BukovecEmpire Down Development 适用于:Mi ...

  8. Qt 环境下的activex控件编程-------1

    本人第一次接触这种activeX控件的东西,参考了网上很多的教程,终于耗时三个多小时初步理解并编写了一个小demo,现在分享给大家,希望大家少走弯路.步骤如下: 1>像平常创建项目一样创建一个d ...

  9. 让动态创建的ActiveX控件响应Windows消息

    当我们通过 CWnd::CreateControl() 动态创建 ActiveX   控件时, Windows 消息并不会被发送给我 们的由   CWnd 派生得控件类.例如,即使我们为 WM_KIL ...

随机推荐

  1. Opencv 改进的外接矩形合并拼接方法

    上一篇中的方法存在的问题是矩形框不够精确,而且效果不能达到要求 这里使用凸包检测的方法,并将原来膨胀系数由20缩小到5,达到了更好的效果 效果图: 效果图: 代码: #include <open ...

  2. Android二维码工具zxing使用

    二维码在我们生活中随处可见.在我眼里简直能够用"泛滥"来形容啦.那怎样在我们Android项目中扫描识别二维码或生成二维码图片呢? 我们通常使用的开源框架是zxing.在githu ...

  3. 百科知识 hta文件如何打开

    后缀名为hta是什么文件,谢谢? 2006-10-11 21:36 提问者: tanhailong2006 | 浏览次数:2092次 我来帮他解答 输入内容已经达到长度限制 还能输入 9999 字 插 ...

  4. loarocks install loadcaffe 失败

    loarocks install loadcaffe 失败 1.Error: Your user does not have write permissions in /home/zhangliang ...

  5. python(31)- 模块练习

    1. 小程序:根据用户输入选择可以完成以下功能:     创意文件,如果路径不存在,创建文件夹后再创建文件     能够查看当前路径     在当前目录及其所有子目录下查找文件名包含指定字符串的文件 ...

  6. XML(四)dom4j解析XML

    使用dom4j须要导入jar包 jar包下载地址:http://pan.baidu.com/s/1o65jWRw 将dom4j-1.6.1.jar包导入Eclipse book2.xml <?x ...

  7. 升级iOS8和iOS9系统后,保险箱Pro、私人保险箱、私密相冊打开就闪退的官方解决方式

    升级iOS8和iOS9.iOS10系统后,保险箱Pro.私人保险箱.私密相冊打开就闪退的官方解决方式 查看设备iOS操作系统版本号号办法:iPhone/iPad->设置->通用->关 ...

  8. Kubernetes基本概念之Label

    系列目录 在为对象定义好Label后,其他对象就可以通过Label来对对象进行引用.Label的最常见的用法便是通过spec.selector来引用对象. apiVersion: v1 kind: R ...

  9. npm 淘宝设置代理

    直接安装cnpm导致无限索引,因此直接使用代理 方法一: 直接在当前用户文件夹下,npmrc 文件上直接设置代理:registry=https://registry.npm.taobao.org 方法 ...

  10. iOS学习之iOS沙盒(sandbox)机制和文件操作1

    iOS学习之iOS沙盒(sandbox)机制和文件操作 接上篇 iOS学习之iOS沙盒(sandbox)机制和文件操作(一) 我们看看如何获取应用程序沙盒目录.包括真机的沙盒的目录. 1.获取程序的H ...