浏览器插件之ActiveX开发

更新时间:2023-05-21 17:49:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

浏览器插件之ActiveX开发(一)

一般的Web应用对于浏览器插件能不使用的建议尽量不使用,因为其涉及到安全问题以及影响用户安装(或自动下载注册安装)体验问题。在有特殊需求(如涉及数据安全的金融业务数据交互、需插件才能实现的与本地设备的交互等)的情况下可以酌情慎用。

浏览器插件总体可以划分为两大阵营,即IE支持的插件以及非IE支持的插件。本来在Netscape时代,对于浏览器插件是有公用的规范的(NPAPI),一开始所有浏览器都支持该规范,包括IE。后来出于商业原因,微软的IE不再支持NPAPI,改而自己开发了一套基于COM的ActiveX体系,但这个体系对于非IE浏览器是拒绝支持的。所以目前的状况基本是,IE浏览器仅支持ActiveX控件,而Firefox、Chrome等浏览器只支持另一类接口(XPCOM或NPAPI)。要想实现一个Web插件,至少需要同时考虑IE支持的AceiveX版以及非IE支持的Plugin版(Flash等插件对于IE与非IE浏览器都是不同的)。

ActiveX的开发可以用C#、VB及C++等语言。用C++开发ActiveX既可以使用ATL,也可以使用MFC。ATL ActiveX输出文件较小,适合网络传输,但开发复杂度稍大;而MFC ActiveX输出文件稍大(附带必要的MFC dll),但易于上手。本文主要介绍基于MFC的ActiveX开发。

一、创建项目及添加接口

在 2008中,新建一个MFC ActiveX Control项目:

点击―OK‖后将弹出如下对话框:

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

依次点击―Next‖按钮直到―Control Settings‖标签页

:

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

由于本例子只演示仅提供函数接口不基于界面的ActiveX,故―Create control based on‖选择―(none)‖即可。点击"Finish‖按钮,即完成了项目的创建,文件结构如下:

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

右击项目名称,选择―Properties‖,在项目属性对话框中对―All Configurations‖进行配置。在―Configurations Properties->General‖标签页中,―Use of MFC‖选择―Use MFC in a static Library‖,以便编译时将MFC相关库自动和控件一起打包。对于―Character Set‖的选择根据具体情况而定,须注意―Unicode Character Set‖和―Mulity-Byte Character SEt‖对字符处理是完全不一样的(字符编码不一样,需要进行MultiByteToWideChar或WideCharToMultiByte转换)。

注意:创建MFC ActiveX

Control时已经自动给项目添加了.def文件并做好了相应关联。若对配置信息更改后导致编译的ocx注册不成功或提示找不到EntryPoint,可以检查一下Linker->Input的Module Definition File是否配置正确,正常情况下已经自动配置好了,如下图:

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

接下来就可以在ActiveX中添加我们需要与外部交互的接口方法和属性了。选择―Class View‖,右击―MyTestActiveXLib->_DMyTestActiveX‖,在弹出的菜单中可以选择Add Function或Add Property来添加接口方法或接口属性:

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

这里以定义一个LONG AddFun(LONG num1,LONG num2) 的接口函数为例,添加Menthod如下图所示:

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

点击Finish后,即可在―MyTestActiveXCtrl.cpp‖文件找到刚添加的接口函数代码:

在函数体中完成自定义的业务逻辑即可。

二、实现安全接口

上述项目编译后即可生成ocx文件,该ocx即可嵌入html在IE中运行。但如果该ocx对应页面是放在真实的web服务器上,访问该页面执行ActiveX里对应接口时IE将会提示―无相关属性,需要设置其初始化和脚本运行的安全性‖等信息。这是因为ActiveX要在远程IE上执行,需要实现安全接口。有关控件的初始化和脚本安全问题,《再谈IObjectSafety》一文及其引用的Microsoft文章做了较详致描述。

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

对于ATL写的ActiveX,实现IObjectSafety即可,这里有ATL实现安全接口的详细的描述。

对于MFC写的ActiveX,可以通过修改注册表的方式来实现控件的安全性,微软也提供的详细的文档描述。具体实现步骤如下:

1、首先在项目中添加Cathelp.h和Cathelp.cpp两个文件,其内容如下所示。 Cathelp.h

#include "comcat.h"

// Helper function to create a component category and associated // description

HRESULT CreateComponentCategory(CATID catid, WCHAR* catDescription);

// Helper function to register a CLSID as belonging to a component // category

HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid);

// HRESULT UnRegisterCLSIDInCategory - Remove entries from the registry HRESULT UnRegisterCLSIDInCategory(REFCLSID clsid, CATID catid);

Cathelp.cpp

#include "stdafx.h" #include "comcat.h" #include "strsafe.h" #include "objsafe.h"

// HRESULT CreateComponentCategory - Used to register ActiveX control as safe HRESULT CreateComponentCategory(CATID catid, WCHAR *catDescription) {

ICatRegister *pcr = NULL ; HRESULT hr = S_OK ;

hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,

NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr); if (FAILED(hr)) return hr;

// Make sure the HKCR\Component Categories\{..catid...} // key is registered. CATEGORYINFO catinfo;

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

catinfo.catid = catid;

catinfo.lcid = 0x0409 ; // english size_t len;

// Make sure the provided description is not too long. // Only copy the first 127 characters if it is.

// The second parameter of StringCchLength is the maximum // number of characters that may be read into catDescription. // There must be room for a NULL-terminator. The third parameter // contains the number of characters excluding the NULL-terminator. hr = StringCchLength(catDescription, STRSAFE_MAX_CCH, &len); if (SUCCEEDED(hr)) { if (len>127) {

len = 127; } } else {

// TODO: Write an error handler; }

// The second parameter of StringCchCopy is 128 because you need // room for a NULL-terminator.

hr = StringCchCopy(catinfo.szDescription, len + 1, catDescription); // Make sure the description is null terminated. catinfo.szDescription[len + 1] = '\0';

hr = pcr->RegisterCategories(1, &catinfo); pcr->Release();

return hr; }

// HRESULT RegisterCLSIDInCategory -

// Register your component categories information HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid) {

// Register your component categories information. ICatRegister *pcr = NULL ; HRESULT hr = S_OK ;

hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,

NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr); if (SUCCEEDED(hr)) {

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

// Register this category as being "implemented" by the class. CATID rgcatid[1] ; rgcatid[0] = catid;

hr = pcr->RegisterClassImplCategories(clsid, 1, rgcatid); }

if (pcr !=

NULL) pcr->Release();

return hr; }

// HRESULT UnRegisterCLSIDInCategory - Remove entries from the registry HRESULT UnRegisterCLSIDInCategory(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)) {

// Unregister this category as being "implemented" by the class. CATID rgcatid[1] ; rgcatid[0] = catid;

hr = pcr->UnRegisterClassImplCategories(clsid, 1, rgcatid); }

if (pcr != NULL) pcr->Release();

return hr; }

注:Cathelp.cpp中的代码是基于Unicode Character Set的。故项目配置时若改成Multi-Byte Character Set,需对Cathelp.cpp中代码做相应修改,否则编译不过;

2、在MyTestActiveX.cpp文件中,添加CLSID_SafeItem的定义:

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

CLSID_SafeItem的值是根据xxxCtrl.cpp(本例中是MyTestActiveXCtrl.cpp)文件中IMPLEMENT_OLECREATE_EX的定义而来的(实际上就是ActiveX的CLASSID)。本例中MyTestActiveXCtrl.cpp文件中IMPLEMENT_OLECREATE_EX的的值如下:

将―0x1345c26b, 0xe979, 0x45a5, 0x99, 0x7d, 0x94, 0x27, 0xfb, 0x81, 0xe7, 0x7‖简单的在适当位置添加―{‖和―}‖括弧即变成了CLSID_SafeItem的值―0x1345c26b, 0xe979, 0x45a5, {0x99, 0x7d, 0x94, 0x27, 0xfb, 0x81, 0xe7, 0x7}‖。 另外,MyTestActiveX.cpp文件起始处还需要引入如下两个文件方能正常编译:

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

3、修改MyTestActiveX.cpp中DllRegisterServer和DllUnregisterServer函数,代码如下(照抄即可):

// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void) {

HRESULT hr; // HResult used by Safety Functions

AFX_MANAGE_STATE(_afxModuleAddrThis);

if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid)) return ResultFromScode(SELFREG_E_TYPELIB);

if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE)) return ResultFromScode(SELFREG_E_CLASS);

// Mark the control as safe for initializing.

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;

// Mark the control as safe for scripting.

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

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; }

// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void) {

AFX_MANAGE_STATE(_afxModuleAddrThis);

// 删除控件初始化安全入口.

HRESULT hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForInitializing);

if (FAILED(hr)) return hr;

// 删除控件脚本安全入口

hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForScripting);

if (FAILED(hr)) return hr;

if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor)) return ResultFromScode(SELFREG_E_TYPELIB);

if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE)) return ResultFromScode(SELFREG_E_CLASS);

return NOERROR; }

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

注:很多例子里DllUnregisterServer的写法与本文的写法不一致,结果导致卸载控件时(regsvr32 /u xxxx.ocx)出现―调用某某ocx文件的DllUnregisterServer函数出错,错误代码:0x80070002‖错误。究其根源,是DllUnregisterServer中删除注册表的顺序出了问题,―waxgourd0的专栏‖中有篇文章对此做了详尽描述。

4、在解决方案下点击资源文件(Resources->MyTestActiveX.rc),点击右键在弹出的菜单中选择―View Code‖,编辑资源文件信息并确保以下几个项目的正确性:

a) BLOCK的值为―040904e4‖ b) OLESelfRegister的值为―\0‖

c) VarFileInfo中的Translation后对应为―0x0409, 1252‖

到目前为止,可以编译项目,输出的ocx控件是可以正常运行的了。~~~

浏览器插件之ActiveX开发(二)

按照上文《浏览器插件之ActiveX开发(一)》的步骤,能开发一个基于MFC的简单的ActiveX控件。不过在实际操作中还是会遇到一些问题。由于对COM编程了解得很少很少,有些问题我也没有找到很好的解决方法。

一、ActiveX需要引用其他dll的问题

我们的ActiveX需要对IC卡设备进行读写,所以需要调用设备自带的接口。设备厂商提供了―mwhrf_bj.lib‖、―mwhrf_bj.dll‖和―mwrf32.h‖等接口文件。将―mwhrf_bj.lib‖和―mwrf32.h‖添加到项目中,ActiveX的接口方法中就可以调用接口文件中的方法了。但是在编译时会出现―Project:error PRJ0050:未能注册输出。请尝试启用―每个用户的重定向‖,或用提升权限从命令提示窗口中注册该组件‖或―

Project : error PRJ0050: Failed to register

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

output. Please ensure you have the appropriate permissions to modify the registry‖的错误。

实际上该错误不是出现在编辑阶段,而是出现在注册编译后的ocx文件时。 2008默认在编译成功后会自动注册编译后的ocx文件。右击项目名称,选择―Properties‖,在弹出对话框的―Configurations Properties->Linker->General‖中的Register Output就可以配置编译后是否自动注册ocx,如下图所示:

之所以注册ocx时出错,是因为注册时找不到被调用的―mwhrf_bj.dll‖文件。将被调用的―mwhrf_bj.dll‖文件放在ocx文件相同目录下或者其他%PATH%路径下(如Windows文件夹或System32文件夹等),则注册ocx时不会报错。在开发环境中可以直接将要被调用的外部dll文件copy到Debug或Release目录下即可,也可以在PreBuild脚本里将外部dll文件COPY到编译目标文件夹,如:

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

注:可参考―/lidabo/archive/2012/07/16/2593604.html‖文章。

二、ActiveX的调试方法

在 2008下可以对ActiveX按如下方式进行调试:

1、准备好Demo.html文件并写好测试程序,该页面中需通过<object />来引用需测试的ocx控件(关于如何在html页面中调用控件在后续文章将专门提及)。

2、在 2008中右击项目名称,选择―Properties‖,在弹出框中的Debugging配置页里配置好Command和CommandArgs参数:

Command: 本地IE浏览器的路径,如―C:\Program Files\Internet Explorer\IEXPLORE.EXE‖

Command Args: 已经创建好的用于测试ocx的html文件路径(如上面提及的Demo.html文件路径)

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

3、在程序中需调试的地方设置断点。按F5运行后将自动启动IE并打开对应的html测试文件,在断点处会中断运行进入调试状态。

三、ActiveX的接口实现out/ref参数及返回自定义结构体数据的问题

有时候ActiveX的接口方法只返回一个数据并不能满足我们的实际要求。例如通过ActiveX的getPersons()方法返回一堆的人员信息,那必定是一个列表或数组,而且每个Person还包含姓名、性别等各种信息,这个时候返回值就相当复杂了。

为了简单起见,还是已通过ActiveX进行读卡号来举例。一般情况下,只要该插件提供以下接口即可满足需求:

BSTR ReadCardNo();

这样在javascript中调用该ActiveX的ReadCardNo()方法即可返回一个包含卡号的字符串。

但是,仅仅提供这个接口如何来识别读卡过程中出现的异常呢?如果读卡操作一切正常,返回一个卡号字符串当然没有问题。但如果读卡过程中出现诸如读卡设备未正确连接、卡无法识别等情况,如果将这些异常信息反馈给调用者呢?

1、首先我想到的是使用ref或out参数来解决,对应C++里是OUT/RETVAL之类的参数修饰符。

在.idl中定义接口为:

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

[id(1), helpstring("方法ReadCardNo")] LONG GetSheetName([out]BSTR* cardNo);

对应接口原型为:

LONG ReadCardNo( BSTR* cardNo );

这样的话通过LONG类型的返回值来识别返回状态,例如可以约定: 0-读卡成功 1-读卡设备未连接 2-未找到可识别的卡 ……

如果返回值为0,表示读卡成功,读出的卡号已通过out类型的参数cardNo传递给调用者。

但是,javascript等脚本语言并不支持out/ref等类型的参数,函数参数也无法传址,所以这个方案无法解决我的实际问题。

2、如果ActiveX的接口能返回一个自定义的结构体类型数据就能满足我们的需求了。例如我们定义一个结构体: typedef struct {

LONG ResultStatus, // 返回状态 0-读卡成功 1-读卡设备未连接 3-未找到可识别的卡

BSTR CardNo // 读卡成功时,保存读取的卡号 } AOPResult;

对应接口如果可以按如下样子来实现就可以解决我们的问题了: AOPResult ReadCardNo();

但是,在MFC ACtiveX的接口定义中中不能直接使用自定义的数据类型的,需要用VARIANT类型来进行转换。下面几篇参考文章均对此有所描述: a) /topics/320146859 b) /topics/20064135

c) /Articles/916/Using-User-Defined-Types-in-COM-ATL

d) 标准MFC WinSock ActiveX控件开发实例(II)高级篇

但实现起来也不是那么容易,鉴于时间问题及我们实际需求的不迫切性,我对此没有做过多尝试。如果有成型实例,望请赐教。

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

3、既然在Web应用场景下ActiveX的接口一般都是供js调用,那么我们可以返回一个json类型的数据即可,如―{ status:0, cardNo:234234344634 }‖。这样ActiveX接口仍然只需返回一个BSTR的参数,只是返回值的意义变了,不是简单的卡号,而需要ActiveX的ReadCardNo接口在内部处理时需要将返回值封装成一个json格式的字符串返回并交由调用方解析。不过,在封装json字符串时需要对{、}、:等特殊字符进行相应处理。

4、对于简单的应用场景,我们也完全可以利用ActiveX的属性来化解此类问题。例如我们在ActiveX中定义一个属性CardNo,这样的话提供的接口只用简单的返回一个状态即可: LONG ReadCardNo()

接口返回值仍然表示状态,如0表示读卡成功,1表示未找到读卡设备等等。当返回0时,读卡成功,对应的卡号从属性CardNo中获取即可。

浏览器插件之ActiveX开发(三)

ActiveX插件如果想在Html中进行引用,必须先对插件ocx文件进行注册,即通过regsvr32将该控件注册到用户的操作系统里。在实际应用中,一般有两种方式来达到这个目的: 一、通过安装程序注册ActiveX

这种方式非常直观,就是制作一个简单的安装程序,该安装程序的任务就是将打包的ocx文件及其依赖文件解压复制到系统目标位置,然后再通过执行regsvr32命令将已复制到用户机器目标位置的ocx文件注册到系统中。当web页面中需要调用相应的ActiveX时,将在显著位置提示用户需下载指定的程序并运行安装。

实际很多应用程序在安装时都隐含地向系统注册了一些ActiveX的,例如QQ、飞信、播放器等,这样相应的web就更加灵活。不过,并不是所有的ActiveX插件都是以ocx文件呈现的,也可以是dll文件。

二、通过cab包由IE自动注册

能否在web页面需要引用ActiveX时由IE自动下载对应的插件并自动安装呢?当然可以。我们要做的就是要将ocx及其他文件打包成一个cab文件,然后将该cab文件放在web服务器上,并在web页面中通过<object ….. codebase=‖xxx.cab#version=1,0,0,1‖ />的方式进行调用。

cab实际上是微软规定的一个特殊格式的压缩文件,制作cab包过程很简单: 1、准备cabarc.exe工具,该工具可以在这里下载,也可以从微软下载;

2、将ocx文件及依赖的其他文件放到同一个目录下,并在该目录下创建一个后缀为.inf的文件(文件名可任意取,一般与ocx文件同名,例如MyTestActiveX.inf),文件内容如下:

[version]

signature="$CHICAGO$"

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

AdvancedINF=2.0

[Add.Code]

MyTestActiveX.ocx=MyTestActiveX.ocx mwhrf_bj.dll = mwhrf_bj.dll

[MyTestActiveX.ocx] file=thiscab

clsid={1345C26B-E979-45A5-997D-9427FB81E707} FileVersion=1,0,0,1 RegisterServer=yes DestDir=11

[mwhrf_bj.dll] file=thiscab

FileVersion=1,0,0,0 DestDir=10

a) signature="$CHICAGO$"表示这个.INF文件和Windows95或其后版本和Windows NT 4.0或其后的版本兼容。

b) [Add.Code]下面的内容用于定义该cab需要下载的各文件对应的定义区块,左边为文件名,等号右边为定义区域名,一般为易读均将定义的区域名与文件名相同。 c) 各文件的定义区域分别定义了该文件的各种属性:

file:表示该文件的获取位置,此处thiscab表示该文件就包含在该cab中;如果在其他位置而不在cab包中,则可以写上具体的位置如http://xxx.xxx.xxx/xx/mwhrf_bj.dll clsid:只有需要注册的ocx文件才设置这个属性,他的值就是改ocx的唯一classid,可以从项目的.idl文件中最下方查找;

关于浏览器Activex插件的开发,对开发有指导作用,可以一看。

FileVersion:文件版本号。一般将ocx文件的版本号视同为整个cab的版本号,在<object codebase=‖xx.cab#version=1,0,0,1‖中将用到该版本号。

DestDir:该文件需要COPY到目标机器的位置,11表示system32目录下,10表示windows目录下,……

有关inf文件的具体内容可参考以下文章相关部分,已经非常详细了: OCX控件CAB打包手册及升级方法 INF文件的语法解说 创建 CAB 文件

VC2005从开发MFC ActiveX ocx控件到发布到.net网站的全部过程 注:如果考虑到终端用户的权限以及将ActiveX注册到什么位置(Current User或Machine),可参考

Non-Admin ActiveX Controls

3、运行如下命令进行打包:

cabarc" -s 6144 N "xxxxxx.cab" "xxxxx.ocx" "mwhrf_bj.dll" "xxxxxx.inf" 其中凡是需要打包的文件均要一一列出,inf文件放在最后(未测试是否必须最后)。文件路径均可以是绝对路径或相对路径,不一定非得是相同文件夹下。 命令执行后将自动生成.cab文件。

浏览器插件之ActiveX开发(四)

简单总结一下前几篇文章的内容,《浏览器插件之ActiveX开发(一)》简单介绍了一下如何在 2008下用C++开发基于MFC的ActiveX插件,《浏览器插件之ActiveX开发(二)》介绍了开发插件时可能遇到的问题,《浏览器插件之ActiveX开发(三)》介绍了如何注册插件以及如何打包成cab文件。但是,到目前为止还没有专门提及如何在Web页面中调用插件,本文主要针对这个问题进行展开。

一、用<Object>标签调用ActiveX 1、Object标签基本用法

在Html页面中调用ActiveX插件最简单常用的方法是:

<object id="CardAccessor"

classid="clsid:03AD53E8-D7E7-485D-A39A-D07B37DEFBC9" width="0" height="0"> </object>

本文来源:https://www.bwwdw.com/article/ytm4.html

Top