天天看点

ATL和MFC,用哪种框架来创建ActiveX控件:第三部分

关于例子应用

这里我将使用的例子是一个通过一个钩子(hook)程序监控消息流的ActiveX控件,它实时显示消息流图。这两个控件实际上有着相同的功能。它们都把图表送到屏幕。它们都带流入接口以便包容器能通知控件开始和停止该图表。它们都支持图表线的颜色和消息间隔长度作为可以持续存在的属性。最后,它们都支持缺省事件集,将关于在一个特定时间段里处理的消息的数量通知包容器。图三显示了这两个控件。

ATL和MFC,用哪种框架来创建ActiveX控件:第三部分
图三 监视 ActiveX 控件消息

用MFC开发一个控件

用MFC开发一个ActiveX控件涉及到在Visual Studio®.中使用ActiveX ControlWizard。为了开始一个新的控件,从File菜单中选择New,然后从工程类型列表中选择MFC ActiveX控件Wizard。首先,ControlWizard要求你决定在DLL中包括多少个控件。接着你就可以选择你打算怎样实现你的控件。

ControlWizard提供的第一批选项总体上适用于控件的DLL。它们包括了许可支持、源码注释和在线帮助。如果选择控件有运行时的许可使得(licensing),ControlWizard将使用BEGIN_OLEFACTORY和END_OLEFACTORY (而非DECLARE__OLECREATE)。BEGIN_OLEFACTORY 和 END_OLEFACTORY宏覆盖了VerifyUserLicense和GetLicenseKey,因而为你的控件提供许可支持。请求ControlWizard包括注释将所有的TODO注释加入代码中。最后,请求ControlWizard包括在线帮助将为DLL创建样板HELP文件源代码。

一旦你通过了第一个对话框,ControlWizard就显示一个对话框用来配置DLL中的控件。这些配置选择包括使控件在运行时是否可见、使控件在可见时激活、对象可以被插入,是否给控件一个About对话框、是否使控件作为一个简单的容器控件。

ControlWizard还有一个将控件实现为一个标准的Windows控件的选项,就像一个编辑框或者一个按钮。这是一个有趣的选项。例如,如果你选择将控件做成一个按钮的子类(或者说子集),控件的窗口实际上是一个按钮。此时,PreCreateWindow截获控件窗口的创建,当创建控件的窗口时使用BUTTON窗口类。

ControlWizard使你可以选择一些高级的选项,包括无窗口的激活,使你的控件具有无剪裁的设备上下文,实现无抖动的激活,使你的控件在非激活状态也接受鼠标消息,使你的控件异步加载自己的属性。下面是在“高级(Advanced)”按钮中的每个选项如何影响ControlWizard生成代码的纲要:

无窗口的激活(Windowless activation)此选项覆盖COleControl::GetControlFlags,将windowlessActivate标志附加到控件标志中。一旦使此选项有效,包容器就将输入消息送交到控件的IOleInPlaceObjectWindowless接口。此接口的COleControl实现通过你控件的消息映射分发消息。你就能简单地通过添加相应的入口到消息映射表,像处理一般windows消息那样处理消息了。

无剪裁的设备上下文(Unclipped device context)选择了此选项覆盖COleControl::GetControlFlags并关闭clipPaintDC位,从而在COleControl的 OnPaint函数中去掉了IntersectClipRect调用。如果你确定你的控件并不需要在客户区外部绘图,这个选项就有用了,因为使得对IntersectClipRect的调用失效后,有一个明显的速度的提高。

无抖动的激活(Flicker-free activation)选择此选项覆盖COleControl::GetControlFlags,将缺省控件标志与noFlickerActivate逐位相或。控件在激活的时候检查此标志以阻止控件在激活和非激活状态转换时被重画。如果你的控件在激活和非激活状态外观一样,那么这个选项就特别有用。

非激活时的鼠标指针通知(Mouse pointer notifications when inactive)这个选项覆盖COleControl::GetControlFlags并附加了pointerInactive位。IPointerInactive接口使得一个对象大多数时间保持非激活,还是仍然要参与到与鼠标的某些交互操作中,例如拖放。

优化的绘图码(Optimized drawing code)这个选项覆盖COleControl::GetControlFlags,打开canOptimizeDraw位,具有优化绘图代码的控件检查这个标志(通过COleControl的IsOptimizedDraw函数)来确定控件是否需要在完成绘画后将旧的对象复原回设备上下文。

异步加载属性(Load properties asynchronously)此选项将库存(stock) ReadyState属性和库存(stock)ReadyStateChange事件加入到控件中去。这将使控件异步加载其属性。例如,一个加载大量数据作为其属性之一的控件会需要很长的时间来加载,而锁住了控件。这个stock属性和事件使得此控件立刻开始加载过程。包容器使用此事件和属性判断控件何时完成加载。

当ControlWizard完成这些事情后,你就得到了编译到一个包含此控件的DLL的源代码(扩展名是.OCX)。由wizard产生的源代码包括一个从COleControlModule(它又是从CwinApp中派生的)中派生的类。这个类包含整个控件模块的初始化代码。接着,wizard为基于COleControl的表示每个控件的类生成源代码。最后,wizard生成一些ODL代码用来建立类型信息。

一旦wizard产生了控件DLL,你就面临开发完善这个控件的任务了。这意味着要添加功能代码,开发一个流入接口(方法和属性),创建并制作属性页,暴露某些事件。但在开始所有这些工作之前,我们先来看一下使用ATL创建一个控件都需要什么步骤。

用ATL开发一个控件

有了基于MFC的控件,你就可以用ATL COM App Wizard得到一个开发基于ATL的控件的触发器。使用ATL来创建控件可以分为两步。MFC的Control Wizard在开始要求你预先确定你希望在DLL中包含多少个控件,而 ATL COM Wizard 只简单地创建DLL——你可以以后从“Insert”菜单中使用“New ATL Object...”选项添加控件。当创建一个新的基于ATL的DLL时,你可以选择混入MFC支持。你还可以选择在控件的DLL中合并任何proxy/stub代码。这使得如果有人希望远程控制你的控件实现的接口,你只需要发布一个文件即可。

一旦生成了基于ATL的DLL,你就可以开始添加COM类了。“Insert”=>“New ATL Object”菜单项使得这项工作变得十分容易。选择此菜单项显示一个对话框,可以创建大量COM类中的任何一个,包括无格式的COM对象、ActiveX控件以及Microsoft事务服务器(MTS)组件(Windows NT Server的一部分)。

当添加基于ATL的控件到你的工程的时候,ATL Object Wizard比MFC Object Wizard提供了更大范围的选项。对于新手来说,ATL使得你可以选择使用任何现有的线性模型实现你的控件。你可以将你的类标记为单线程(single threaded)或者单元(apartment threaded)线程的。ATL Object Wizard限制你创建一个自由的(free threaded)或者双线程(both threaded)的控件,因为控件一般都是有用户界面(UI)的。

如果你创建了一个单线程(single threaded)控件,一个包容控件的客户端将总是将它加载到主单线程的单元(STA)中。结果,只有运行在客户进程空间中的单主线程才会接触到你的对象,这样就免除了并发访问时你必须保护控件状态的义务。另外,因为你的对象的所有实例将只会被一个线程接触,你就不必担心DLL中的任何全局数据。

如果你的控件是单元线程(apartment threaded)的,你也还免去了保护控件内部状态的大部分负担。但是,你仍然必须保护DLL中的全局数据。为什么呢?首先,设想你的控件是由客户的单主线程创建的。现在假定客户试图创建该控件的另一份拷贝——从一个运行在当前进程的多线程单元中。通过将你的对象标记为单元线程的,你告诉COM你希望你的控件免遭并发访问。COM在它加载时为你的控件创建一个新的STA。现在当线程调用进入你的对象时,它们不得不通过单元边界来访问它,偏远层将同步对此对象的调用。然而,当一个特定控件的状态被保护不被并发访问,作为一个在STA中的副产品,由控件的实例所共享的数据(象在DLL中的全局数据一样)是易受攻击的。这是因为你的全局DLL数据(同时为几个对象服务,分别运行在独立的线程中)会被那些多线程同时接触到。

虽然基于MFC的COM类总是可聚合的(内置的支持),ATL ObjectWizard可以让你指定你的控件选项:是否支持聚合,只是可聚合的,或者是独立的对象。根据你选择的聚合选项,ATL ObjectWizard使用一个宏来强行聚合策略。例如,缺省的COM类的实现是可聚合的——对象将既运行在独立的模式,又作为一个聚合的一部分。如果你让你的COM对象不可聚合,ObjectWizard把DECLARE_NOT_ AGGREGATABLE宏加到你的类定义中。如果你选择了仅是可聚合的,ObjectWizard把DECLARE_ ONLY_AGGREGATABLE宏加入到类定义中。

这里是宏如何工作的。缺省的对象创建发生在一个名为_CreatorClass的类中。_CreatorClass当被加入到服务器范围的对象映射后(这是OBJECT_ENTRY宏所做的工作的一部分),就成为你的COM类的创建机制。_CreatorClass其实只是一个名为CComCreator2类的别名,此类将两个从CComCreator类中定制的类作为参数。此宏根据选择的聚合模式来特制CComCreator类,分别使用CComObject, CComAggObject, CComFailCreator, 或者CcomPolyObject:

#define DECLARE_NOT_AGGREGATABLE(x) public: \
   typedef CComCreator2< CComCreator< CComObject< x > >, \
   CComFailCreator<CLASS_E_NOAGGREGATION> > _CreatorClass;
#define DECLARE_AGGREGATABLE(x) public: \
   typedef CComCreator2< CComCreator< CComObject< x > >, \
   CComCreator< CComAggObject< x > > > _CreatorClass;
#define DECLARE_ONLY_AGGREGATABLE(x) public: \
   typedef CComCreator2< CComFailCreator<E_FAIL>, \
   CComCreator< CComAggObject< x > > > _CreatorClass;
#define DECLARE_POLY_AGGREGATABLE(x) public: \
   typedef CComCreator< CComPolyObject< x > > _CreatorClass;

      

ATL ObjectWizard Attributes页中最后三个检查框包括对COM异常处理的支持(例如,ISupportErrorInfo接口),连接点以及自由线程(free threaded marshaler)集(FTM)。你也可以添加ISupportErrorInfo到控件的继承列表中,提供ISupportErrorInfo::InterfaceSupportsErrorInfo的一个实现。打开连接点将添加IConnectionPointImpl 模板类到控件的继承列表中。

如果两个对象正好位于同一个进程中,请聚合你的控件到FTM以便单元间(以及Windows 2000的上下文间)的调用更为有效地发生。然而,当你使用FTM编写控件时,如果或多或少地违反了单元(以及Windows 2000的上下文)规则,就绝不要选择这个选项。

除了你可以应用到所有COM对象的常规选项,ATL ObjectWizard还提供了几个控件创建特定的选项。首先,ATL ObjectWizard让你从一个常规控件(例如一个按钮或是一个编辑控件)中进行子类划分。你可以为你的控件指定其它几个选项使得它更加不透明,给它一个更实心的背景,在运行时不可见,或者是你的控件象一个按钮那样的工作。下面是控件属性页提供的一个选项纲要:

不透明和实心背景(Opaque and solid background)如果你想要确保在控件边界之后不显示任何的包容器,选择"opaque"检查框,这是控件传给它的包容器的状态信息。结果是,控件将画出它的完整矩形。选择此选项设置VIEWSTATUS_OPAQUE位以便IViewObjectExImpl::GetViewStatus向包容器指示一个不透明的控件。你可能还想选择一个实心的背景。这个选项设置VIEWSTATUS_ SOLIDBKGND 位以便GetViewStatus指示控件有一个实心的背景。

运行时不可见(Invisible at runtime)此选项使你的控件在运行时不可见。你可以使用不可见控件在后台完成某些操作,例如周期性的激发事件。此选项在它加入到注册表中后使得控件翻转OLEMISC_INVISIBLEATRUNTIME 位。

仿按钮(Acts like button)此选项使你的控件象一个按钮那样工作。此时,控件将在包容器周围属性DisplayAsDefault的基础上显示为缺省的按钮。如果控件的位置标记为缺省按钮,控件将显示为一个较厚的框架。选择此选项在它加入到注册表中后使得控件翻转OLEMISC_ACTSLIKEBUTTON 位。

仿标签(Acts like label)选择此选项使得你的控件取代包容器的内部标签。这使得控件在它加入到注册表中后标记OLEMISC_ACTSLIKELABEL。

在超类基础上添加控件(Add control based on superclass)选择此选项使得你的控件根据一种标准window类进行子类划分。下拉列表包含了Windows定义的window类。当你选择这些类名中的一个时,向导添加一个CContainedWindow成员变量到你的控件类中。CContainedWindow::Create将你指定的window类超类化。

规格化DC(Normalize DC)选择此选项使得你的控件在被调用来绘制自己时创建一个规格化的设备上下文。这标准化了控件的外观,但是效率降低了。此选项生成的代码覆盖了OnDrawAdvanced方法(而不是常规的OnDraw方法)。

可插入的(Insertable)选择此选项使得你的控件显示在象Microsoft Excel 和Word 这样的应用的“Insert Object”对话框中。你的控件就能够被插入到任何支持嵌入对象的应用中了。选择此选项在注册表项中增加了Insertable键。

仅为窗口化的(Windowed only)选择此选项迫使你的控件窗口化,即使在支持无窗口对象的包容器中。如果你不选择此选项,你的控件将会自动的适应包容器:在支持无窗口对象的包容器中是无窗口的,在不支持无窗口对象的包容器中是有窗口的。这将使CComControlBase::m_bWindowOnly标志设置为TRUE。ATL使用此标志来决定在控件激活过程中是否要查询包容器的IOleInPlaceSiteWindowless接口。

ATL要求你预先在“Stock Properties”页中决定你的对象的库存(stock)属性,你可以选择Caption或者 Border Color这样的属性,或者通过点击>>按钮一次性选择所有的库存(stock)属性。这将向控件的属性映射中添加属性。

在运行ATL COM App Wizard 和 ObjectWizard之后,你就得到了一个完整的DLL,它具有一个COM DLL所必需的所有环节。此控件暴露的众所周知的输出包括DllGetClassObject, DllCanUnloadNow, DllRegisterServer,和 DllUnregisterServer。另外,你得到了一个满足COM主要需求的对象——包括一个主流入接口和一个类对象。

一旦你已经使用一个向导开始了一个工程,下一步就是使控件做点有趣的事情了。通常出发点是控件的功能(rendering)代码。你立刻得到一些可视化的反馈。在下一部分中,我们将揭示实现一个基于MFC的控件的功能代码的全部细节。(待续)

继续阅读