使用回调接口实现ActiveX控件和它的容器程序的通讯
本文阅读基础:有一定的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文件的开头下加入以下内容:
- import "oaidl.idl";
- import "ocidl.idl";
- [
- object,
- uuid(87C3DA69-C915-41f5-8142-D77816F22004), // 这个IID 可以用GUDIGEN.EXE 产生
- helpstring("ICallBack 接口"),
- pointer_default(unique)
- ]
- interface ICallBack : IUnknown {
- [id(1),helpstring("回调接口,响应鼠标按下")] HRESULT FireLButtonDown([in] LONG x,LONG y);
- [id(2),helpstring("回调接口,响应鼠标移动")] HRESULT FireMouseMove([in] LONG x,LONG y);
- [id(3),helpstring("回调接口,响应鼠标按下")] HRESULT FireLButtonUp([in] LONG x,LONG y);
- };
由于画图不是本文描述的重点,这里暂且略过。下面我们开始在ActiveX控件上实现绘图和添加传递回调接口的函数。这里所说的传递回调接口实现的功能是把客户端程序的回调接口派生类对象的指针传到ActiveX控件内部。具体如下:
这里介绍一下添加COM接口,首先在对应的接口选择:Add Method,如下图:
然后开始填充返回类型、参数等信息:
我发现必须选择下拉列表的参数类型,不选择的话就会出错:
这时我们需要要SetCallBackPtr函数中的IUnknown全部替换为ICallBack。为什么要进行替换?一方面ICallBack*类型才是我们真正要传递的指针类型;另一方面如果不进行替换,外部的客户端程序将不能识别ICallBack这个类型。我发现在idl文件定义的类型,似乎要在library ***
{
}
这一段出现过的类型外部才能识别,不过这个没有得到验证,有空得研究一个idl文件。这里大致要替换的地方包括三处,控件窗口类中的头文件的声明、cpp文件中的定义以及在idl文件中的声明。这一步算是解决了回调接口ICallBack的外部识别问题。
现在我们在CDrawView定义一个
ICallBack* m_pCallBack;的数据成员,以及它的赋值接口函数:
- void CDrawView::SetCallBack(ICallBack* pCallBack)
- {
- if (NULL!=pCallBack)
- {
- m_pCallBack = pCallBack;
- }
- }
这里经常会出现的一个错误是:use of undefined type 'ICallBack'。这里的根源在于C++源码文件并不能识别idl文件中的类型,就是一个内部识别问题。后来我发现其实编译所有COM工程都有一步就是将idl文件的内容翻译为C++能够识别的头文件。所以你会发现编译后工程文件夹会多出一些头文件和C文件,其中一个头文件的命令比较有意思,规律大致是工程名+idl.h。这个头文件其实就是编译idl文件生成的。因此要使用回调基类接口,就必须包含这个头文件。我这个工程的是:DataXidl.h。打开这个文件,可以从中看到COM的一些奥秘,里面有这样一段代码:
- ICallBack : public IUnknown
- {
- public:
- virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE FireLButtonDown(
- /* [in] */ LONG x,
- /* [in] */ LONG y) = 0;
- virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE FireMouseMove(
- /* [in] */ LONG x,
- /* [in] */ LONG y) = 0;
- virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE FireLButtonUp(
- /* [in] */ LONG x,
- /* [in] */ LONG y) = 0;
- };
看来在编译过程中已经将idl接口的代码转换为C++代码了。
然后我们在控件窗口类里添加一个画图视图的指针:
- CDrawView *m_pView;
实现刚才添加的接口函数,把回调接口传给视图类:
- void CDataXCtrl::SetCallBackPtr(ICallBack* pCallBack)
- {
- AFX_MANAGE_STATE(AfxGetStaticModuleState());
- // TODO: Add your dispatch handler code here
- if (NULL!=m_pView)
- {
- m_pView->SetCallBack(pCallBack);
- }
- }
4. 在派生类中实现派生类接口
现在我们开始新建一个单文档程序:CallbackTest,VS 2005风格的。来测试这个ActiveX控件。
我们首先将ActiveX接口导出,具体如下:
在工程上添加新类,选择"MFC Class From ActiveX Control":
在注册表上选择DataX控件,再接口_DDataX从生成类,如下图:
再增加一个接口器类CSink,用于接收ActiveX控件发送过来的通知:
这里会出现ICallBack未定义类型的问题。这时我只需要在stdafx.h把它的类型库或ocx导入进来即可,代码如下:
- #ifdef _DEBUG
- #import "../Intdir/Debug/DataX/DataX.tlb" no_namespace
- #else
- #import "../Intdir/Release/DataX/DataX.tlb" no_namespace
- #endif
你也可以这样写:
- #ifdef _DEBUG
- #import "../outdir/Debug/DataX.ocx" no_namespace
- #else
- #import "../outdir/Release/DataX.ocx" no_namespace
- #endif
效果是一样。我推荐你使用后一种用法,因为一般对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函数。
首先在应用程序类定义一个保护数据成员:
- CSink m_Sink;
再在InitInstance函数的最后面添加如下代码:
- CMainFrame* pFrame = static_cast<CMainFrame*>(m_pMainWnd);
- if (NULL!=pFrame)
- {
- // 获取视图指针
- CTestCallbackView *pView = static_cast<CTestCallbackView*>(pFrame->GetActiveView());
- // 获取ActiveX控件指针
- CDDataX* pDataX = pFrame->GetActiveControl();
- // 传递回调接口指针
- pDataX->SetCallBackPtr(&m_Sink);
- // 设置视图窗口为发送消息的目标窗口
- m_Sink.SetMsgWnd(pView->GetSafeHwnd());
- }
下面以鼠标右键按下消息的回调代码。首先在ActiveX控件的视图类的鼠标按下消息响应函数:
- void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)
- {
- // TODO: Add your message handler code here and/or call default
- m_BeginPoint = point;
- if (NULL!=m_pCallBack)
- {
- // 激发客户端的CSink::raw_FireLButtonDown函数
- m_pCallBack->FireLButtonDown(point.x,point.y);
- }
- CView::OnLButtonDown(nFlags, point);
- }
客户端的CSink对象收到通知后,给视图类发送鼠标按下消息:
- STDMETHODIMP CSink::raw_FireLButtonDown(long x,long y)
- {
- if (NULL!=m_hMsgWnd)
- {
- LPARAM lparam = MAKELPARAM(x,y);
- ::PostMessage(m_hMsgWnd,WM_LBUTTONDOWN,NULL,lparam);
- }
- return S_OK;
- }
效果图如下:
这种回调机制,在现在的开源或商业产品有没有应用实例呢?如果你研究过著名的开源的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。
源码下载:点击下载 。
from:http://blog.csdn.net/clever101/article/details/5649489
使用回调接口实现ActiveX控件和它的容器程序的通讯的更多相关文章
- [转]C#开发ActiveX控件,.NET开发OCX控件案例
引自:百度 http://hi.baidu.com/yanzuoguang/blog/item/fe11974edf52873aaec3ab42.html 讲下什么是ActiveX控件,到底有什么 ...
- MFC 之ActiveX控件学习
本文将介绍ActiveX控件的应用与工作原理,读者可以把ActiveX控件看成一个极小服务器的应用程序,它不能独立运行,必须要嵌入到容器程序中与容器一起运行,就像电脑主机中的显卡,它自己在电脑硬件系统 ...
- 以编程方式使用 Microsoft Office Visio 2003 ActiveX 控件
以编程方式使用 Microsoft Office Visio 2003 ActiveX 控件 2007/10/29 Mark BukovecEmpire Down Development 适用于:Mi ...
- ActiveX 控件漏洞挖掘之方法
ActiveX是微软公司提出,并在1996年被正式命名的组件技术.该技术提供了一种通用的开放程序接口,使用这种技术开发的ActiveX控件可以直接集成到IE浏览器或第三方应用程序中,但由于第三方编程等 ...
- 建立对ActiveX控件的了解
本文来自百度百科:ActiveX控件 ActiveX是Microsoft对于一系列策略性面向对象程序技术和工具的称呼,其中主要的技术是组件对象模型(COM).在有目录和其它支持的网络中,COM变成 ...
- 使用C#开发ActiveX控件[Obsolete]
文章出处:http://www.cnblogs.com/yilin/archive/2009/09/15/1567332.html 附件下载(源代码+安装文件+教程) 0. 前言 ActiveX控件以 ...
- MFC-[转]基于MFC的ActiveX控件开发
作者:lidan | 出处:博客园 | 2012/3/13 16:10:34 | 阅读22次 ActiveX 控件是基于组件对象模型 (COM) 的可重用软件组件,广泛应用于桌面及Web应用中.在VC ...
- C#开发ActiveX控件,.NET开发OCX控件案例 【转】
http://xiaochen.2003.4.blog.163.com/blog/static/480409672012530227678/ 讲下什么是ActiveX控件,到底有什么作用?在网页中又如 ...
- COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)
声明:本文代码基于CodeProject的文章<A Complete ActiveX Web Control Tutorial>修改而来,因此同样遵循Code Project Open L ...
随机推荐
- c# 柱状图(转载)
// c# 显示柱状图 using System; using System.Data; using System.Configuration; using System.Web; using Sys ...
- Linux 文件内容转码
文件内容的转换: iconv -f GB2312 -t UTF-8 gb1.txt >gb2.txt-f, –from-code=名称 原始文本编码-t, –to-code=名称 输出编码-o, ...
- win7系统下连接使用mac 蓝牙键盘(Apple Wireless Keyborad)
这几天买了一个apple wireless keyborad 玩玩,主要是给孩子买了一个ipad 搭配上wireless keyborad让她玩app足够了,就当一部电脑用吧. 看起来挺精致的,可以了 ...
- hdu 4455 Substrings(找规律&DP)
Substrings Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Tota ...
- codeforces 630P. Area of a Star
题目链接 圆上n个点等距离分布, 求构成的星星的面积. 我们可以求三角形OAB的面积, ∠CAE = 1/2 ∠ COE = PI/n, 那么∠CAO = PI/2n, ∠AOB非常好求, 就是PI/ ...
- zoj 2587 Unique Attack 最小割判定
题目链接 让你判断最小割是否唯一. 判断方法是, 先求一遍最大流, 然后从源点dfs一次, 搜索未饱和边的数目. 从汇点dfs一次, 同样也是搜索未饱和边的数目, 看总和是否等于n. 如果等于n那么唯 ...
- js跨域及解决方法
什么是跨域 JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象.但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦.这里把涉及到跨域的一些问题简单地整理一下: 首 ...
- 转: requestAnimationFrame,Web中写动画的另一种选择
HTML5/CSS3时代,我们要在web里做动画选择其实已经很多了: 你可以用CSS3的animattion+keyframes; 你也可以用css3的transition; 你还可以用通过在canv ...
- NYOJ 527 AC_mm玩dota
AC_mm玩dota 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描述 大家都知道AC_mm比较喜欢玩游戏,特别是擅长war3这款经典游戏.某天AC_mm来到了VS平台上 ...
- linux之多进程fork:进程通信
++++++++++++++++++信号机制+++++++++++++++++++ 接收信号 int signal(int sig,__sighandler_t handler); int func( ...