作者:朱金灿
来源:http://blog.csdn.net/clever101/
本文阅读基础:有一定的C++基础知识(了解继承、回调函数),对MFC的消息机制有一定了解,对COM的基础知识有一定了解,对ActiveX控件有一定了解。
一. 前言
ActiveX控件和它的容器程序如何通讯是一个值得研究的问题,因为这涉及到ActiveX控件和它的容器程序如何交互的问题。VC知识库的杨老师写了一系列博客介绍了一些通讯方式。链接如下:
COM 组件设计与应用(十三)--事件和通知(VC6.0)
COM 组件设计与应用(十四)--事件和通知(vc.net)
COM 组件设计与应用(十五)--事件和通知(VC6.0)
COM 组件设计与应用(十六)--事件和通知(vc.net)
这些文章写得真的很好,语言幽默风趣,深入浅出。我看后决心把它应用在ActiveX控件的回调实现上,经过实践,觉得有些地方语焉不详,自己做些摸索,写就此文,算是对杨老师文章的一点补充。
二.通知的方法
ActiveX控件是一个窗口,它的容器程序自然也有一个父窗口;同时ActiveX控件是一个接口;ActiveX控件本质是一个COM组件,COM组件的客户端和服务器端本身有自己的通讯方式。从这两点我们可以想到二者之间的几种通讯方式:
我和我的同事曾争论ActiveX控件接口能否像一般C++的DLL那样在导出函数参数列表里设置一个回调函数指针那样实现回调,那时我认为是不行的。现在我看了ActiveX控件接口的参数类型,更加坚定了我的看法。其实从COM的初衷来看应该也是不行的,因为COM的初衷之一是提供一种跨语言的调用接口,而回调函数指针只对客户端是C++程序是有意义,对于VB、C#则无回调函数指针一说的。
三.实践检验
现在我们编一个这样的小程序:在ActiveX控件上画直线,在画直线的同时把坐标传给客户端的视图,在客户端的视图区上依据传进来的坐标信息,绘制出相应的直线。
在动手之前我简要介绍我的思路:所谓基于COM的回调虚接口实现ActiveX控件和客户端程序的通讯,大致是这样的,就是在ActiveX工程的内部的idl文件定义一个虚接口,在客户端程序定义一个虚接口的派生类来实现回调函数,在客户端程序传递派生类对象指针给ActiveX控件,在控件内部调用这个虚接口的函数来激发客户端程序的派生类的对应的回调函数。这里其实有一个关键问题,就是定义在idl文件中回调虚接口如何被ActiveX工程和客户端程序识别,而不至于成为未定义类型(说实话,这个问题折磨了我两个晚上,之所以这么麻烦,大概因为这个接口是定义在idl文件,而不是C++源文件中),下面我将介绍如何解决这个问题。
首先我们创建一个MFC ActiveX Control的工程:DataX,具体如下图:
接着在idl文件添加回调接口。这一步需要手动编辑idl文件。首先实现使用GUIDGEN.EXE(该工具在$/Microsoft Visual Studio 9.0/Common7/Tools路径下,在VC6.0,VC 8.0都有这个工具)产生一个IID,生成时注意选择是注册表形式,具体如下图:
然后在idl文件的开头下加入以下内容:
由于画图不是本文描述的重点,这里暂且略过。下面我们开始在ActiveX控件上实现绘图和添加传递回调接口的函数。这里所说的传递回调接口实现的功能是把客户端程序的回调接口派生类对象的指针传到ActiveX控件内部。具体如下:
这里介绍一下添加COM接口,首先在对应的接口选择:Add Method,如下图:
然后开始填充返回类型、参数等信息:
我发现必须选择下拉列表的参数类型,不选择的话就会出错:
这时我们需要要SetCallBackPtr函数中的IUnknown全部替换为ICallBack。为什么要进行替换?一方面ICallBack*类型才是我们真正要传递的指针类型;另一方面如果不进行替换,外部的客户端程序将不能识别ICallBack这个类型。我发现在idl文件定义的类型,似乎要在library ***
{
}
这一段出现过的类型外部才能识别,不过这个没有得到验证,有空得研究一个idl文件。这里大致要替换的地方包括三处,控件窗口类中的头文件的声明、cpp文件中的定义以及在idl文件中的声明。这一步算是解决了回调接口ICallBack的外部识别问题。
现在我们在CDrawView定义一个
ICallBack* m_pCallBack;的数据成员,以及它的赋值接口函数:
这里经常会出现的一个错误是:use of undefined type 'ICallBack'。这里的根源在于C++源码文件并不能识别idl文件中的类型,就是一个内部识别问题。后来我发现其实编译所有COM工程都有一步就是将idl文件的内容翻译为C++能够识别的头文件。所以你会发现编译后工程文件夹会多出一些头文件和C文件,其中一个头文件的命令比较有意思,规律大致是工程名+idl.h。这个头文件其实就是编译idl文件生成的。因此要使用回调基类接口,就必须包含这个头文件。我这个工程的是:DataXidl.h。打开这个文件,可以从中看到COM的一些奥秘,里面有这样一段代码:
看来在编译过程中已经将idl接口的代码转换为C++代码了。
然后我们在控件窗口类里添加一个画图视图的指针:
实现刚才添加的接口函数,把回调接口传给视图类:
4. 在派生类中实现派生类接口
现在我们开始新建一个单文档程序:CallbackTest,VS 2005风格的。来测试这个ActiveX控件。
我们首先将ActiveX接口导出,具体如下:
在工程上添加新类,选择"MFC Class From ActiveX Control":
在注册表上选择DataX控件,再接口_DDataX从生成类,如下图:
再增加一个接口器类CSink,用于接收ActiveX控件发送过来的通知:
这里会出现ICallBack未定义类型的问题。这时我只需要在stdafx.h把它的类型库或ocx导入进来即可,代码如下:
你也可以这样写:
效果是一样。我推荐你使用后一种用法,因为一般对ActiveX控件而言,别人可能会提供一个ocx文件给你,而不是一个tlb文件给你。
接着我们定义一个停靠栏类class CDrawBar : public CDockablePane专门负责放置ActiveX控件。
编译一下,你会发现一下子出现了7个错误,本质上只有一个错误:error C2259: 'CSink' : cannot instantiate abstract class。CSink不能实例化对象。我们明白了,CSink派生自一个虚接口,却没有实现它的纯虚函数。那么ICallBack类中有几个我们还没实现的纯虚函数呢?我数了一下,有六个:IUnknown::QueryInterface、IUnknown::AddRef、IUnknown::Release、ICallBack::raw_FireLButtonDown、ICallBack::raw_FireMouseMove、ICallBack::raw_FireLButtonUp'。前三个函数我们很容易明白,ICallBack派生自IUnknown类,IUnknown的三个纯虚函数自然也得实现(这三个函数的意义如果你没搞懂,我推荐你看潘爱民的《COM原理与应用》)。至于后面三个函数,一开始我百思不得其解,我记得我在ICallBack类定义函数名称是:FireLButtonDown、FireMouseMove和FireLButtonUp,怎么到了外部变成了:raw_FireLButtonDown、raw_FireMouseMove、raw_FireLButtonUp。后来我大致分析,可能是编译器将名称做了转换,VS上的说法是:Raw methods provided by interface。这里的回调流程是怎样的呢,我以下面的图来说明:
其中传递回调接口的派生类工作放在应用程序类的InitInstance函数。
首先在应用程序类定义一个保护数据成员:
再在InitInstance函数的最后面添加如下代码:
下面以鼠标右键按下消息的回调代码。首先在ActiveX控件的视图类的鼠标按下消息响应函数:
客户端的CSink对象收到通知后,给视图类发送鼠标按下消息:
效果图如下:
这种回调机制,在现在的开源或商业产品有没有应用实例呢?如果你研究过著名的开源的ActiveX控件:MapWinGIS。你会发现MapWinGIS实际上是采用这种回调机制的,比如MapWinGIS中的操作shp文件的类CShapefile的一个接口:
STDMETHOD(Open)(/*[in]*/ BSTR ShapefileName, /*[in, optional]*/ICallback * cBack, /*[out, retval]*/VARIANT_BOOL * retval);
这个函数的第二个参数的类型就是MapWinGIS内部定义的回调接口类型:ICallback。
四.小结
总结一下开发过程,再和杨老师提供的例程做一个简单比较。应该说我这个例程和杨老师的例程很类似。有点不同的是杨老师使用的是标准的ATL 组件,我这个使用的是MFC ActiveX控件。从本质而言,ActiveX控件类是一个com接口,只不过这个接口有些特殊,它既是一个窗口,因而也就具有了一般窗口的属性,能够接受和响应消息,同时它也是一个接口类。
本文出现的代码编译环境为:VS C++ 2008 + sp1, Win XP + sp3。
源码下载:点击下载
。
分享到:
相关推荐
在多线程环境中,ActiveX控件能够与JavaScript进行通信,实现异步回调,提高应用程序的响应速度和效率。 描述中提到的"ActiveX OCX控件"是指在Windows平台上使用的一种插件技术,OCX是控件容器,可以包含各种用户...
9. **容器和宿主**:动态控件可以被其他应用程序(容器)嵌入和使用。容器负责管理和显示控件,而控件则作为独立的组件提供服务。 通过以上技术,开发者可以在VC++中创建出功能丰富、交互性强的动态控件,提升软件...
ActiveX控件是一种可以被多种应用程序使用的组件,它允许开发者将复杂的交互式媒体,如Flash动画,整合到他们的应用程序中。 "F_in_box"可能是一个自定义的容器类,用于包装和管理Flash控件。在项目中,开发者可能...
LabVIEW中内置了对ActiveX的良好支持,通过使用自动化引用(Automation Reference)功能面板控件,用户可以方便地调用外部程序如Word的控件,从而实现与外部程序间的通信。 **调用Word的基本步骤:** 1. **打开...
总的来说,`MediaSongReloadFFTAnalysis.fig` 和 `MediaSongReloadFFTAnalysis.m` 文件共同构成了一个完整的MATLAB GUI,它实现了从加载媒体文件到播放,再到进行FFT分析的功能。通过理解并研究这些源码,你可以学习...
ActiveX控件容器是指能够承载并显示ActiveX控件的应用程序。这种容器允许用户在容器应用程序中插入和使用各种ActiveX控件。 #### ActiveX控件接口向导 (ActiveX Control Interface Wizard) ActiveX控件接口向导是...
在WPF中实现Flash播放和循环截图涉及到对WPF与Windows Forms互操作的理解以及使用Flash ActiveX控件。本文将详细介绍这一过程。 首先,由于WPF自身不支持直接嵌入COM组件,如Flash的ActiveX控件,我们需要利用...
在Visual C++中播放.avi视频文件是...总之,通过Visual C++结合DirectShow或ActiveX控件,我们可以轻松地在Windows应用程序中播放.avi视频文件。理解这些知识点和实现步骤,将有助于开发者创建自己的多媒体播放器程序。
7. **网络编程**:MFC包含MFC ActiveX控件和ATL(Active Template Library),使得开发COM组件和进行网络通信变得更加简单。 8. **国际化和本地化**:MFC支持资源文件(.rc)的使用,可以方便地实现多语言支持,...
5. 回调函数:在JavaScript中定义回调函数,处理地图加载、点击事件等,然后通过Web浏览器控件的DocumentComplete事件来触发这些函数。 在"testhtml"这个压缩包文件中,很可能包含了用于演示上述功能的HTML文件。你...
SkinMagic 2.21 动态库版本的使用和 Skin++动态库及静态库版本的使用。 VC++精仿QQ2008窗体及分类菜单 VC++卡通风格气泡提示源码 VC++实现任务管理器源码 任务管理器应该大家都很熟悉,论坛里也有好多的任务管理器...
16.5.3执行回调技术623 第17章ActiveX控件627 17.1 ActiveX控件基础知识627 17.1.1创建UserControl模块627 17.1.2运行ActiveX Control Interface wizard629 17.1.3添加欠缺的部分631 17.2 UserControl对象635 17.2.1...
16.5.3执行回调技术623 第17章ActiveX控件627 17.1 ActiveX控件基础知识627 17.1.1创建UserControl模块627 17.1.2运行ActiveX Control Interface wizard629 17.1.3添加欠缺的部分631 17.2 UserControl对象635 17.2.1...
SkinMagic 2.21 动态库版本的使用和 Skin++动态库及静态库版本的使用。 VC++精仿QQ2008窗体及分类菜单 VC++卡通风格气泡提示源码 VC++实现任务管理器源码 任务管理器应该大家都很熟悉,论坛里也有好多的任务管理器...
SkinMagic 2.21 动态库版本的使用和 Skin++动态库及静态库版本的使用。 VC++精仿QQ2008窗体及分类菜单 VC++卡通风格气泡提示源码 VC++实现任务管理器源码 任务管理器应该大家都很熟悉,论坛里也有好多的任务管理器...