ActiveX组件及其注册
前言:
随着计算机软件编程技术的发展和应用的需要,人们越来越倚重于ActiveX组件。那么什么是ActiveX组件呢?在此笔者给出简单的介绍。另外,关于如何对ActiveX组件进行注册,在目前所见到的资料中间,难以得到一个比较完全答复。尤其,关于ActiveX EXE 注册的文章更是微乎其微,而且即使给出的方法也比较片面、单一。鉴于此,笔者通过大量的查阅和比较深入的研究,在此对ActiveX组件的注册问题给出一个比较完全、系统的总结。希望给对此感兴趣的朋友一个参考。
一.概述
在接下来的部分,首先将就ActiveX组件的概念以及它的分类进行简要的介绍,从而让读者对ActiveX组件有一个概念上的把握。随后,将就如何注册ActiveX组件的问题进行比较深入详细的解释。分别给出ActiveX 控件、ActiveX DLL、ActiveX EXE的具体注册方法。并在此基础上,对如何在制作安装程序并安装后,实现对 ActiveX组件的注册给出了若干解决方案。
二.ActiveX组件的概念
在我们平常的阅读和编程学习中,我们经常可以看到诸如“ActiveX控件”,“ActiveX组件”等名词。那么什么是“ActiveX控件”,什么是“ActiveX组件”呢,它们有什么区别呢?
首先,我们应该知道,组件是建立在 ActiveX技术上的代码的独立单元,用于通过特定的接口提供特定的一组服务。它提供客户端在运行时所请求的对象。在 Remote Data Service 中,当组件包括支持业务进程的关键字逻辑时也被称为“业务对象”。
其次,ActiveX是使软件组件能够在网络环境中交互作用而与创建组件的语言无关的一套封装技术。实现ActiveX 的基础是“组件对象模型”(Component Object Model ,缩写为COM)。它也是提供封装 COM组件并将其置入应用程序(如(但不限于)Web 浏览器)的一种方法。
最后,上面一段话中提到的COM(Components Object Model)是软件组件互相通讯的一种方式。它是一种二进制和网络标准,允许任意两个组件互相通讯,而不管它们是在什么计算机上运行(只要计算机是相连的),不管各计算机运行的是什么操作系统(只要该操作系统支持 COM),也不管该组件是用什么语言编写的。COM 还提供了位置透明性:在编写组件时,其他组件是进程内 DLL、本地 EXE 还是位于其他计算机上的组件,都不会产生太大影响。
这样以来,我们可以给ActiveX组件(ActiveX component)一个定义,就是:一个应用程序或开发工具,可以使用另一个应用程序支持的对象,或者提供自己的对象供另一个应用程序使用。以前这些程序和对象称为“OLE 自动服务程序”和“OLE 自动服务器”。
至于ActiveX控件,则只是ActiveX组件的一个分类,是一个标准的用户接口元素,是具有 .OCX 文件扩展名或者可插入对象的文件,能够快速地把窗体和对话框组装起来;当使用自定义控件对话框将其增加到工程中去时,工具箱将被扩展。在以前ActiveX控件被称作 OLE 控件。ActiveX 控件还使 Internet 更加生动,为 World Wide Web 页增加了有趣的新功能。
三.ActiveX组件的分类
ActiveX组件可以分为三类:
● ActiveX控件
● ActiveX文档
● 代码成分(OLE自动服务器)
(1)ActiveX控件
可以从无到有被用户完全建立,它能被建立在另一个控件之上,或者它可容纳多个已经有的控件。比如 VB中的ActiveX控件就是建立在VB的用户控件对象上的。当创建一个ActiveX控件时,就创建了一个带有扩展名为 .ctl 的控件类文件。我们通常使用这种文件来创建实际的控件,其扩展名为 .ocx 。
(2)ActiveX文档
通常,在具体的编程环境如VB 、VC++中,可以用传统文档的语义学来创建完全的应用程序。换句话说,它不仅有应用程序的功能,而且还有文档性能的灵活性 ─ 当用户打开一个 ActiveX文档时,将不仅拥有应用程序的全部功能,而且能保持和发布应用程序原有数据的“备份”,因而,“文档”实际上是主动的。比如, VB的ActiveX文档是建立在VB用户文档的基础上的。创建一个ActiveX文档时,用带有扩展名 .dob 的文件保存。VB使用这种DOB文件来创建EXE或DLL文件,这种EXE或DLL文件可以为ActiveX文挡提供实际的代码。另外VB生成一个带扩展名 .vbd 的说明文件,它对ActiveX文档进行描述,用主应用程序可以打开这个文件。对于ActiveX文档,可以让用户保存数据,这些数据被存在VBD文件里。
(3)代码成分
以前被称作OLE自动服务器。这些对象可以让用户在其他程序里使用其代码。比如,有一个陈列在代码成分里的时钟例程,这样以来此例程可以被其他程序使用。代码成分(ActiveX EXE, ActiveX DLL)能够支持属性与方法。
四.建立ActiveX组件的选择
ActiveX组件的各个内容,它们的应用是不同的。那么如何进行选择,以来确定究竟该创建那种组件呢?
首先,在这里我们务必弄清楚ActiveX组件的运行方式,并知道相应的特点。一般来说,ActiveX组件有两种运行方式:它们是进程外服务器运行和进程内服务器运行。
如果把一个ActiveX组件作为一个可执行文件(EXE文件)的一部分,那么它就是一个进程外服务器并在自己的进程内运行。若把它作为一个动态链接库(DLL文件)的一部分,则它是一个进程内服务器并作为客户应用程序在统一进程里运行。若用户的ActiveX组件是一个进程外服务器,他就是一个可以单独运行的EXE文件。使用进程内服务器的应用程序通常比使用进程外服务器的应用程序运行速度快,因为应用程序没有通过进程边界去使用对象属性,方法与事件。
如果将建立的ActiveX文档作为一个进程内组件(DLL文件),进程内组件的性能超过同样编译成EXE的组件。另外,如果多个程序访问同一个EXE文件,将会覆盖全局数据;如果他们都有自己的进程内服务器,这种事情不会发生。
客户应用程序和内进程组件共享相同的内存空间,所以在调试内进程代码部件的方法时,可以用客户应用程序的堆栈输参数。而对于外进程组件来说,在调试代码部件的过程中,方法的参数必须被移动来通过两个进程间的内存界线,这被称为marshaling。
这样,我们可以得出如下关于如何选择所创建组件类型的结论:
(1)若想建立一个在可调用的代码中提供例程的不可视组件,可以建立一个代码组件(ActiveX EXE 或AtiveX DLL).
(2)若想建立一个能在同一个进程中与应用程序运行的组件, 可以建立一个AtiveX DLL.
(3)若想建立一个能服务多个应用程序又能在一台远程计算机上运行的组件,可以建立一个AtiveX EXE。
(4)若想建立一个可视组件,并在设计时能被拖动到一个应用程序中,可建立一个AtiveX 控件。
(5)若想建立一个可视组件,并在运行时能接受一个应用程序窗口,可建立一个ActiveX 文档。
至于创建组件之后,如何编程,如何被使用,可以参照有关编程方面的书籍。在这里,我们假设ActiveX组件的设计已经完成,然后对ActiveX组件的注册进行较为详细的讨论。
五.ActiveX组件注册的几种方法
不同的ActiveX组件,它们注册有着不同的方法,现在就针对ActiveX控件、ActiveX DLL 、ActiveX EXE三种不同的组件分别给出解决方案。
1.ActiveX控件的注册
ActiveX 控件与开发平台无关,在一种编程语言中开发出来的ActiveX 控件,几乎不作任何修改,便可以在另一种编程语言中使用。但是ActiveX 控件被开发出来以后,要想在Windows中被正确使用,首先必须将控件文件复制到硬盘中,然后在Windows中进行注册。未在Windows中注册过的ActiveX 控件是不能被使用的。一般注册VB6.0 ActiveX 控件的方法有如下几种:
(1)使用Regsvr32.exe 程序对VB ActiveX 控件进行注册。 该文件位于Windows目录的system子目录下。使用方法如下:点击“开始”,在弹出的菜单中再点击“运行”,在出现的对话框中输入以下命令:
<控件路径和ActiveX 控件文件名> 注册一个ActiveX 控件。此处 regsvr32的路径名可以省略。而且一般可将被注册的ActiveX 控件拷贝到\windows\system下,这样我们也不用在注册时输入控件的路径了。
如果想要解除对某一个ActiveX 控件的注册,只需要在regsvr32后面加一个参数“/u”,即 <被注册过的ActiveX 控件文件名> 。
(2)利用某些编程环境中的浏览功能。比如在VB6.0中可以点击界面上的“工程”,在弹出的下拉菜单中,点击“部件”一项,随后出现了一个新的界面。在此界面上利用“浏览”按钮,找到并选中需要注册的控件,确定后便注册到“部件”界面的“控件”栏里。
利用上面两种方法进行控件注册后,便可以让部件开发人员在实际的编程中,使用该控件了。
(3)使用安装程序制作软件——InstallShield 。使用 Regsvr32.exe 程序来注册ActiveX 控件,以及利用“浏览”来注册,虽然简单,但是都需要手工注册,在不用时,还需要手工解除,所以对于一个使用了该控件的应用程序来说并不实用。一般大型的应用软件都需要一个安装程序,在安装程序中解决ActiveX 控件注册就非常实用了。使用InstallShield可以制作出专业级的安装程序,还可以注册其中的ActiveX 控件;而且卸载软件时,可以自动注销以前注册的ActiveX 控件。其做法就是按照InstallShield 的向导,进行安装程序的制作,在进行到最后一步,点击“Finish”后,“InstallShield”将进行下一步的“详细定制”。选择“File Groups”选项,将其中包含需要自注册ActiveX控件文件项的“Self-Registered”属性改为“yes”.
(4)安装过程中的自注册。 ActiveX控件在安装的时候必须被注册,方可以在应用程序中被调用。往往利用编程工具自带的安装制作工具可以达到这个目的。仍以VB6.0为例。利用VB6.0本身自带的创建安装程序的工具软件:Package & Deployment Wizard。只要将ActiveX控件包含在发布的文件中间,Package & Deployment Wizard将根据需要自动将该控件打成自注册文件的属性。万一,我们没有成功的话,可以修改安装程序的安装文件列表 setup.lst。将相应宏中的参数设置为DLLSelfRegister。如果不需要自注册,可以将该项删除(注意逗号要保留)。
例如下面是作者自己编的一个ActiveX控件,利用上述方法创建安装程序后,其后面的参数变为:
[Setup1 Files]
File1=@clock1.ocx,$(WinSysPath),$(DLLSelfRegister),$(Shared),2/19/01 9:27:30 AM,36864,1.0.0.0
其中,加粗的一项 $(DLLSelfRegister) 就是标明自注册的宏参数设置。可以手工加或删。
(5)补充一点。有些公司开发的ActiveX控件注册需要利用附带的专门的工具软件。运行该工具,就可以将相应的ActiveX控件注册。这仍然属于手工注册,这些ActiveX控件被发布时,一般享有版权,同时会有专门的说明。
2.ActiveX DLL的注册
ActiveX DLL的注册与ActiveX控件的注册基本上相似,上述用于ActiveX控件注册的方法基本都适用于ActiveX DLL的注册。在此可以参照上述有关ActiveX控件注册的方法进行注册。
3.ActiveX EXE的注册
作为一种进程外运行的组件,ActiveX EXE的注册方法异于ActiveX控件和ActiveX DLL的注册,并且在运用中有一定的难度。在此,将它们的注册方法总结如下:
(1)“浏览”的方法。以VB6.0为例。在VB6.0编程界面上,进入“工程”,在弹出的下拉菜单中间选择“引用”,然后在出现的新界面上点击“浏览”,找到并选中需要注册的组件,确定后,便可以在列表中间看到对应项了。
(2)直接运行的方法。注册进程外组件ActiveX EXE时,只要在VB6.0的环境中运行一下该组件代码,便可以实现注册了。这时ActiveX EXE的信息被加入到Windows注册表中。但是,必须注意,此信息只是在VB开发环境中运行此程序时被临时加入。当程序停止时,有关如何访问这些对象的信息便从系统中清除掉。
(3)利用安装制作工具。比如在VB6.0中,可以利用VB6.0本身自带的创建安装程序的工具软件:Package & Deployment Wizard。在创建安装程序的过程中,自动将其变为自注册。若不然,同样可以修改安装程序的安装文件列表 setup.lst。将相应宏中的参数设置为EXESelfRegister。如果不需要自注册,可以将该项删除(注意逗号要保留)。
例如下面是作者自己编的一个进程外组件 ,利用上述方法创建安装程序后,其后面的参数变为:
[Setup1 Files]
File1=@CommThread.exe,$(WinSysPath),$(EXESelfRegister),$(Shared),12/25/00 8:47:44 PM,57344,1.0.0.0
其中,加粗的一项 $(EXESelfRegister) 就是标明自注册的宏参数设置。可以手工加或删。
(4)如果打算把进程外服务器安装到另外一台机器上自注册,而且脱离编程环境时,可以先将文件拷贝到另外机器上,执行时在命令行上加上参数 “ /regserver”,如果取消注册,可以在命令行上加上“ /unregserver”。使用 /regserver命令选项注册进程外COM服务器时,可以不去理会手工启动时程序工作的正常方式。Sub Main或Class_Initialize程序都不会被调用,只有由当该编程环境包括在EXE文件中的注册逻辑才会运行。
六.总结
上面,就日常的学习中所理解的有关ActiveX 组件方面的概念性的东西,作了一些简单的总结。在,注册方面,给出了一些较为实用的方法。当然,我们也可以自己动手,亲自编制一些小程序,实现ActiveX 控件的注册。如在VC++中,使用Windows API函数LoadLibrary载入ActiveX控件;再使用GetProcAddress函数获取ActiveX 控件中注册函数DLLRegisterServer(注销函数为DLLUnRegisterServer)指针;最后再调用注册函数DLLRegisterServer(或注销函数为DLLUnRegisterServer)。感兴趣的,可以参照相关的资料,自己动手进行编程。
当然,在实际编程应用中,往往需要自注册。对于ActiveX 控件和ActiveX DLL可以在制作安装程序后,安装时自动注册。而对于ActiveX EXE 可能要麻烦一些,我们不可能
在每次运行软件时,先在编程环境中,通过代码的编译或浏览来注册。通过实践,总结出如下的方法来实现自注册:
(1)利用编程工具自带的制作安装程序的工具,来制作安装程序,安装时,可以自动实现ActiveX EXE的自注册。
(2)利用加上参数 /regserver,实现自注册。在VB6.0编程中,当需要用到ActiveX EXE时,通过VB6.0的系统函数shell(),同时进行调用和自注册。命令格式为:
shell “[path\] filename /regserser”
卸载时,只需将 /regserser 替换为 /unregserser就行了。假如组件文件与应用程序在同一个目录中或在 \windows\system\下,组件文件路径名可以省略。
(3)假如想用Installshield为应用程序制作专业级的安装程序,同时又需要ActiveX EXE的自注册,可以将应用程序用Installshield打包,而ActiveX EXE用VB6.0打包,制作出两个安装程序。在使用时,两个安装程序都运行一次,这样,也可以实现要求,只是比较麻烦。
ActiveX控件的安全初始化和脚本操作
简 介
很多微软的ActiveX控件(本地/远程)都需要使用持久性数据进行初始化,而且它们大多数都是可以通过脚本进行操作的 (支持一个方法,事件和属性的集合提供脚本语言操作)。初始化(使用持久性数据)和脚本操作都需要一个确定的安全性机制保证其安全性不被违背。
一个说明控件初始化安全性风险的例子就是压缩/解压控件。如果用户点击了一个包含木马程序的远端系统文件(诸如Kernel.dll) 并且要求组件解压该文件,系统安全性就已经被破坏了。
一个说明控件脚本操作安全性风险的例子就是一个基于系统设置的控件在无法获得相应设置之前就被执行。因此开发者有义务提供必要的代码在运行脚本执行之前获得相应的系统设置信息。
IE3的安全层次
IE3具有三个安全性层次: 低级,中级和高级。等用户试图显示一个包含非安全性初始化和脚本操作的控件的页面时,IE3给出下列基于当前安全级别的警告信息。
低级:没有警告
中级:提示用户潜在的风险
高级:提示用户潜在的风险并且禁止控件运行
注意: 大多数控件开发者都是将它们的IE3的安全性级别设置为最低以便于开发调试。但是在实施安全性初始化和脚本操作代码之前,安全级别应该重新设置为高级(缺省)来确保提供测试足够的可信度。
初始化安全性
当一个控件初始化时,可以从一个 IPersist* 接口获得数据 (来自一个本地/远端的URL)提供初始化状态。这是一个潜在的安全隐患,因为数据可能来自一个不可信的数据源。不提供安全性保证的控件将无视数据源的安全性。
有两种方法可以检测控件的初始化安全性。第一种使用组件分组管理器(Component Categories Manager)创建一个正确的入口到系统注册表。IE3检测注册表之后才调用你的控件决定是否这些入口存在。第二种方法实现一个名称为IObjectSafe的接口到你的控件。如果IE3发现你的控件支持IObjectSafety,它调用 IObjectSafety::SetInterfaceSafetyOptions 方法然后才载入你的控件。
脚本操作安全性
代码签字可以保证用户其代码的可信度。但是运行一个 ActiveX 控件可以被脚本访问将带来几个新的安全性问题。即使控件被认为是可靠的,如果使用不可信脚本代码访问也不能保证其安全性。比如,微软的 Word 被认为是一个安全的程序,但是一个宏可以使用自动化模型的脚本删除用户计算机上的文件,载入宏病毒或者蠕虫病毒。
有两种方法提供你的控件的脚本操作安全性保证。第一种是使用组件分组管理器 (Component Categories Manager) -- 在组件导入以后在注册表上面创建正确的入口。IE3在脚本操作之前检查注册表确认安全性。第二种是实现一个 IObjectSafety 接口到你的控件。如果IE3发现你的控件支持 IObjectSafety,就在导入控件之前调用 IObjectSafety::SetInterfaceSafetyOptions 方法来确保安全性脚本操作。
使用组件分组管理器
正像我们前面提到的,IE3通过检测注册表绝对一个控件是否是可以安全性初始化和脚本操作的。IE3通过调用 ICatInformation::IsClassOfCategories 方法决定是否控件支持给出的安全性分组。
如果一个控件使用组件分组管理器将自己注册为安全的,该控件的注册表入口就包含一个实现的分组关键字,该关键字含有一个或者两个子键。一个子键设置控件支持安全性初始化,另一个设置支持安全性脚本操作。安全性初始化子键依赖于 CATID_SafeForInitializing; 安全性脚本操作子键依赖于 CATID_SafeForScripting。(其他组件分组子键定义在 Comcat.h 文件,而安全性初始化和脚本操作子键定义在 Objsafe.h 文件。)
下列演示显示了一个注册表入口(Tabular Data Control),该ActiveX控件同IE绑定支持独则创建数据驱动的网页。因为控件是可以安全性脚本操作和初始化的,注册表中将其标记为安全性脚本操作(7DD95801-9882-11CF-9FA9-00AA006C42C4) 且安全性初始化(7DD95802-9882-11CF-9FA9-00AA006C42C4)。
将一个控件注册为安全的
系统注册表含有一个组件分组键来罗列每一个安装在系统中的组件的功能性分组。下面演示了一个组件分组键。假设 CATID_SafeForScripting (7DD95801-9882-11CF-9FA9-00AA006C42C4) 和 CATID_SafeForInitializing (7DD95802-9882-11CF-9FA9-00AA006C42C4) 在列表之中。
要创建一个组件分组的子键,你的控件必须包含以下步骤:
★ 创建一个组件分组管理器(Component Categories Manager)实例来接收 ICatRegister 接口的地址。
★ 设置正确的 CATEGORYINFO 结构分量。
★ 调用 ICatRegister::RegisterCategories 方法,将初始化的 CATEGORYINFO 结构变量传递给这个方法。
下面的例子演示如何使用这些步骤来完成和合并一个名称为 CreateComponentCategory 函数到实例控件。
#include "comcat.h "
HRESULT CreateComponentCategory(CATID catid,WCHAR* catDescription)
{
ICatRegister* pcr = NULL ;
HRESULT hr = S_OK ;
file://创建一个组件管理器实例(进程内)
hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL,CLSCTX_INPROC_SERVER,IID_ICatRegister,(void**)&pcr);
if (FAILED(hr))
return hr;
// 确信 HKCR\Component Categories\{..catid...} 键已经被注册
CATEGORYINFO catinfo;
catinfo.catid = catid;
catinfo.lcid = 0x0409 ; // 英语
// 确信提供的描述在127个字符以内
int len = wcslen(catDescription);
if (len> 127)
len = 127;
wcsncpy(catinfo.szDescription,catDescription,len);
// 确信描述使用 '\0 '结束
catinfo.szDescription[len] = '\0 ';
hr = pcr-> RegisterCategories(1,&catinfo);
pcr-> Release();
return hr;
}
当一个子键被创建到需要的分组,控件应该注册到该分组,需要以下步骤:
★ 创建一个组件分组管理器实例接收 ICatRegister 接口地址。
★ 调用 ICatRegister::RegisterClassImplCategories 方法,将控件的 CLSID 和需要的 category ID 作为参数传递给函数。
下面的例子演示如何将一个名称为 RegisterCLSIDInCategory 加入实例控件。
#include "comcat.h "
HRESULT RegisterCLSIDInCategory(REFCLSID clsid,CATID catid)
{
// 注册你的组件分组信息
ICatRegister* pcr = NULL ;
HRESULT hr = S_OK ;
hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL,CLSCTX_INPROC_SERVER,IID_ICatRegister,(void**)&pcr);
if (SUCCEEDED(hr))
{
// 注册已实现的类到分组
CATID rgcatid[1] ;
rgcatid[0] = catid;
hr = pcr-> RegisterClassImplCategories(clsid,1,rgcatid);
}
if (pcr != NULL)
pcr-> Release();
return hr;
}
一个控件应该在调用 DLLRegisterServer 函数是注册安全性初始化和脚本操作。(DLLRegisterServer 由组件对象模型 [COM] 调用创建注册表入口) 在实例组件中 DLLRegisterServer 函数调用了 CreateComponentCategory 和 RegisterCLSIDInCategory 函数 (它们保证控件的安全性初始化和脚本操作)。下面的就是 DLLRegisterServer 的实现。
STDAPI DllRegisterServer(void)
{
HRESULT hr; // return for safety functions
AFX_MANAGE_STATE(_afxModuleAddrThis);
if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(),_tlid))
return ResultFromScode(SELFREG_E_TYPELIB);
if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE))
return ResultFromScode(SELFREG_E_CLASS);
// 注册控件是安全性初始化的
hr = CreateComponentCategory(CATID_SafeForInitializing,L "Controls
safely initializable from persistent data! ");
if (FAILED(hr))
return hr;
hr = RegisterCLSIDInCategory(CLSID_SafeItem,CATID_SafeForInitializing);
if (FAILED(hr))
return hr;
// 注册控件是安全性脚本操作的
hr = CreateComponentCategory(CATID_SafeForScripting,L "Controls
safely scriptable! ");
if (FAILED(hr))
return hr;
hr = RegisterCLSIDInCategory(CLSID_SafeItem,CATID_SafeForScripting);
if (FAILED(hr))
return hr;
return NOERROR;
}
作为一个创建所有安全性分组入口到注册表的控件,它也应该负责卸载所有的分组信息。COM 调用控件的 DLLUnRegisterServer 函数删除相应的注册表入口然后卸载该控件。
要卸载一个安全性初始化和脚本操作控件,控件应该完成以下任务:
★ 创建一个组件分类管理器实例接收 ICatRegister 接口地址。
★ 调用 ICatRegister::UnRegisterClassImplCategories 方法,将控件的 CLSID 和必要的 category ID 作为参数传递