浏览器插件之C++开发ActiveX

来源:互联网 发布:程序员基础书籍 编辑:程序博客网 时间:2024/06/04 00:23

转自:http://blog.csdn.net/SHIJIERUCIMEIHAO/article/details/8827940


网上看到些文章讲述关于C++创建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开发。

一、创建项目及添加接口

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

                           image

    点击“OK”后将弹出如下对话框:

                           image

    依次点击“Next”按钮直到“Control Settings”标签页:

                           image

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

                            image

    右击项目名称,选择“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是否配置正确,正常情况下已经自动配置好了,如下图:

                   image

 

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

                   image

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

                   image

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

                 image

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

 

二、实现安全接口

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

      对于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// descriptionHRESULT CreateComponentCategory(CATID catid, WCHAR* catDescription);// Helper function to register a CLSID as belonging to a component// categoryHRESULT 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;    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))    {       // 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的定义:

         image

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

         image

     将“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文件起始处还需要引入如下两个文件方能正常编译:

      image      

   

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

复制代码
// DllRegisterServer - Adds entries to the system registrySTDAPI 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.     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 registrySTDAPI 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;}
复制代码

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

 

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

          image

        a) BLOCK的值为“040904e4

        b) OLESelfRegister的值为“\0

        c) VarFileInfo中的Translation后对应为“0x0409, 1252” 

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

      吐舌头按照上文的步骤,能开发一个基于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 output. Please ensure you have the appropriate permissions to modify the registry”的错误。

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

       image

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

       image

 

       注:可参考“http://www.cnblogs.com/lidabo/archive/2012/07/16/2593604.html”文章。

 

     二、ActiveX的调试方法

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

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

        2、在vs.net 2008中右击项目名称,选择“Properties”,在弹出框中的Debugging配置页里配置好CommandCommandArgs参数:

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

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

            image

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

 

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

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

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

BSTR ReadCardNo();

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

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

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

     在.idl中定义接口为:

      [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) http://bbs.csdn.net/topics/320146859

          b) http://bbs.csdn.net/topics/20064135

          c) http://www.codeproject.com/Articles/916/Using-User-Defined-Types-in-COM-ATL

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

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

       

      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插件如果想在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$"    AdvancedINF=2.0      [Add.Code]    MyTestActiveX.ocx=MyTestActiveX.ocxmwhrf_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=thiscabFileVersion=1,0,0,0DestDir=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文件中最下方查找;

                         image

           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文件。

    简单总结一下前几篇文章的内容,微笑简单介绍了一下如何在Vs.net 2008下用C++开发基于MFC的ActiveX插件,吐舌头介绍了开发插件时可能遇到的问题,偷笑介绍了如何注册插件以及如何打包成cab文件。但是,到目前为止还没有专门提及如何在Web页面中调用插件,本文主要针对这个问题进行展开。

 大笑一、用<Object>标签调用ActiveX

    1、Object标签基本用法

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

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

    id属性就不用解释了,和html中其他元素的id一样,是DOM树中各元素的唯一标识。width和height表示该ActiveX在Web页面中占位的大小,对于仅提供接口无UI界面的ActiveX来说将其设置为0即可,因为不需要在页面上显示任何内容(对于需要显示界面的ActiveX,需要在项目里创建Dialog及写相应逻辑,可以参考“A Complete ActiveX Web Control Tutorial”实例 )。

    classid属性在这里是一个非常关键的属性,IE正是通过他才能正确找到要调用的ActiveX的。每个ActiveX均有一个唯一的id来表示,这就是classid,在我们创建MFC ActiveX Control项目时Vs.net 2008就帮我们生成了这个id, 可以在程序的.idl文件最下方找到这个ID值:

             image

     一般不建议手动修改程序中的这个uuid值,因为在xxxxCtrl.cpp文件中也用到了这个id值,只是表现形式不一样罢了:

            image

      控件注册成功后,这个classid及控件文件位置等信息均写入注册表了,如下所示:

            image

      当然,如果ActiveX还定义了其他属性,也可以在<object>中以属性的形式给他们赋值。

 

      如果用户的计算机已经注册了该插件(例如通过Setup.exe方式),那么Html引用上段代码后就可以通过js调用插件的接口和属性了(再次提示一下,ActiveX只能在IE浏览器运行,也就是<object…>这段代码在firefox等其他浏览器是不能正常工作的)。   

复制代码
<fieldset>         <legend>Read Card No Testing</legend>         卡号:<input type="text" id="txtCardNo_Read" maxlength="32" class="txt disable" readonly />         <input type="button" id="btnRead" value=" Read CardNo " class="btn" onclick="javascript:readCardNo();" />    </fieldset>        <fieldset>         <legend>Write Card No Testing</legend>         卡号:<input type="text" id="txtCardNo_Write" maxlength="32" class="txt" />         <input type="button" id="btnWrite" value=" Write CardNo " class="btn" onclick="javascript:writeCardNo();" />    </fieldset><script type="text/javascript">    var txtCardNo_Read = document.getElementById("txtCardNo_Read");    var txtCardNo_Write = document.getElementById("txtCardNo_Write");    var objCard = document.getElementById("CardAccessor");       function readCardNo() {        txtCardNo_Read.value = "";                try{            var ret = objCard.ReadCardNo();                        if (ret == 0) {                txtCardNo_Read.value = objCard.CardNo;                alert("读卡成功!");            }            else {                alert("读卡失败!错误码为:" + ret);            }        }        catch (e) {            alert(e.message)        }    }    function writeCardNo() {        var cardNo = txtCardNo_Write.value;        try {            objCard.CardNo = cardNo;            var ret = objCard.WriteCardNo();            if (ret == 0) {                                alert("写卡成功!");            }            else {                alert("写卡失败!错误码为:" + ret);            }        }        catch (e) {            alert(e.message)        }    }      </script>
复制代码

 

    2、使用Object标签时如何判断ActiveX是否已注册

复制代码
<script type="text/javascript">    var objCard = document.getElementById("CardAccessor");    if (objCard.object==null) {        alert("CardAccessor插件未安装!");    }    else{        alert("已检测到CardAccessor插件!");    }</script>
复制代码

        另外也可以通过访问ActiveX中的某个已知已定义的属性来判断插件是否已安装,如果返回的属性值为undefined,则表示没有检测到插件。例如:

复制代码
<script type="text/javascript">    var objCard = document.getElementById("CardAccessor");    if (objCard.CardNo==undefinedl) {        alert("CardAccessor插件未安装!");    }    else{        alert("已检测到CardAccessor插件!");    }</script>
复制代码

 

   3、如何让IE自动下载安装插件并智能升级

       如果检测到插件没有安装,怎样让IE自动从指定位置下载插件并自动安装呢?很简单,在object标签中使用codebase属性即可:

<object id="CardAccessor"     classid="clsid:03AD53E8-D7E7-485D-A39A-D07B37DEFBC9"     codebase="CardAccessor.cab#version=1,0,0,1"    width="0"     height="0"></object>

     codebase的值格式为“xxxxx.cab#version=1,0,0,1”。'#'前面部分为cab文件的位置,可以是在服务器上的绝对位置,也可以是相对位置。'#‘后面部分表示当前引用的cab包的版本号。当IE检测到系统没有注册指定插件,便从codebase指定的位置下载该cab到本地,并按照其中.inf文件的描述将各文件复制到指定位置并注册指定的控件。(注:在实际应用中涉及签名问题,后文再述

     即使本地已经注册了该插件,IE还将拿已注册的控件版本号与codebase中指定的版本号相比较,如果codebase中指定的版本号大于已注册插件的版本号,IE仍然会从codebase指定位置下载cab包并重新注册该插件。

     正如前面的文章所提,为方便管理,一般将cab包中需注册的ocx文件的版本号视同cab版本号。

     当插件或插件依赖的文件需要升级时,只需更新相应的文件,并对.inf中的相应文件版本升级(为方便管理,无论是否更新了ocx文件,该ocx文件的版本号也跟着升级,因为其版本号代表了整个cab),然后重新打包成cab发布到服务器上,并更新html中object标签中codebase属性值的version部分版本号。当用户下次访问该页面时,IE将自动下载升级后的cab并重新注册插件。实际上,经过我的测试,及时服务器上的cab包不做任何变化,只要增加codebase中version的值,对应插件均会重新下载和注册。

 

二、通过javascript的new ActiveXObject来调用ActiveX

      如果不使用object标签,也可以直接通过js的ActiveXObject来创建指定ActiveX的实例从而达到调用插件接口的目的(IE下xmlhttpRequest的调用就是这个原理)。例如:

var objCard = new ActiveXObject("Uprain.CardAccessorCtrl.1");

      如果插件已经注册,接下来就可以通过objCard来调用插件接口和访问属性了。

      ActiveXObject函数的参数为对应插件的ProgId而非CLASSID。在项目中xxxxCtrl.cpp文件中同样可以找到或修改对应插件的ProgId值,如下图:

       image

     即IMPLEMENT_OLECREATE_EX的第二参数就表示当前插件的ProgId,该值可以根据实际需要自行修改。实际上,在注册表中通过ProgId是可以找到对应的ClassId的,两者是有关联的:

        image

 

      那么如何判断ActiveX是否已经安装呢?实际上如果ActiveX未安装,通过new ActiveXObject的方式来创建插件对象是会抛出“Automation 服务器不能创建对象”异常的。所以用如下方式即可:

try {         objCard = new ActiveXObject("Uprain.CardAccessorCtrl.1");                    }catch (e) {         alert("调用ActiveX失败!");    }

 

    不过,我在实际测试过程中遇到两种情况:

     1) 出现“Automation 服务器不能创建对象”异常的并不一定就表示插件没安装,也有可能是因插件未实现初始化或脚本安全接口,从而被IE拦截,需要调整IE“工具-选项-安全-自定义级别”中“ActiveX控件和插件”部分的设置;

     2) 有时候,同样已注册的插件,通过object标签引用的方式能正常调用接口,但通过new ActiveXObject的方式则调用插件接口失败。

     再见前面四篇文章都是在描述如何用C++开发基于MFC的ActiveX插件以及如果对插件进行打包和在Web页面中调用,但确忽略了一个非常重要的问题:代码签名。《浏览器插件之ActiveX开发(三)》提及了两种注册插件的方法,其中IE自动下载并注册插件的方法就涉及到签名问题,如果cab包是未签名的或签名不被信任的,IE就拒绝注册该插件。

     一、数字签名简述

     现在的各种软件星罗密布、鱼龙混杂,用户在使用软件程序时一定要十分谨慎,稍不留意,就被病毒或恶意程序侵害。代码数字签名的出现就在一定程度上解决了这个问题。那么经过数字签名的软件有什么好处?软件一旦经过数字签名,至少可以保证以下两点:

      1)该软件确实是由数字签名证书中显示的软件开发商开发的;

      2)该软件自软件开发商发布以后,没有被第三方做过任何修改。

   

      不过,要完全理解数字签名或PKI(数字签名是PKI的组成部分,Public Key Instructure),还需要理解一下基本常识,例如对称加密、非对称加密、摘要算法、公钥、私钥、数字证书、根证书等等。以下几篇文章均对这些概念做了通俗易懂的解读:

      a) 白话数字签名

      b) CA认证原理以及实现

      c) 了解数字证书

      d) 软件代码数字签名基本原理

      e) 数字签名

      f) 为什么需要PKI

      g) 数字签名(代码签名)流程

      h) 12306在线买火车票为什么需要安装根证书

    

     二、为什么ActiveX需要数字签名?

     由于ActiveX插件在运行时与本地桌面应用程序一样,对用户系统的资源有极大的访问权限,如果让任何ActiveX通过Web页面都能自动下载并注册的话,那对用户必将造成非常大的威胁。所以默认设置下,IE将对需自动下载的cab文件进行数字签名认证,只有经过数字签名了且签名认证通过的cab包才自动下载并注册到用户系统中。

      一般地,不仅仅需要对cab包进行代码签名,在打cab包之前还会对ocx文件进行代码签名。

 

     三、如何进行代码签名?

      1、首先需准备用于代码签名的相关工具SignTool.exe,可以从这里下载。

      2、申请可用于ocx文件签名的代码签名数字证书,一般是需付费的。如果是测试用,可以有几种方式:

          a) 自己创建一个测试签名证书以及根证书;

          b) 可以从www.ca365.com网站申请免费代码签名证书或测试证书(应用时需自动导入根证书到用户系统里);

          c) 借用淘宝的支付宝证书(淘宝对每个支付宝用户都可以颁发一个数字证书)。

 

          申请数字证书的过程实际上是:

           1)  在申请者的计算机上创建一个密钥对,即一个私钥和一个公钥,私钥保留在申请者计算机内,将公钥传送给CA机构;

           2)  CA机构在通过必要的线下审核后(测试证书和免费证书相当于没有审核过程的),用CA自己的私钥对申请者的申请信息进行签名(申请者信息包括申请者传过来的公钥、申请者自身基本信息等),并加上时间戳。CA对申请信息进行加密后就生成了一个证书,一般是以.cer文件的形式下发给申请者。

 

      3、用signTool工具对文件进行签名:

Signtool sign /f "xxxx.pfx" /d "卡设备读写机ActiveX" /du http://www.51diancai.com /t http://timestamp.verisign.com/scripts/timstamp.dll "xxxxx.cab"

         其中:

         xxxx.pfx 是签名证书(实际上包含了代码签名数字证书和私钥,在IE的证书管理容器里“个人”里导出时可以选择包含私钥);

         http://timestamp.verisign.com/scripts/timstamp.dll 是时间戳服务。

 

       有关代码签名以及创建测试签名证书的问题以下文章均有详细介绍,不再赘述:

  •          a) 制作临时证书为ActiveX控件签名
  •          b) 给控件做数字签名
  •          c) 微软代码签名证书使用指南
  •          d) VeriSign代码签名证书技术白皮书_v1.0_090413
  •          e) VC++开发Activex控件以及签名发布
  •           f) VC2005从开发MFC ActiveX ocx控件到发布到.net网站的全部过程  
  •           g) ActiveX的数字签名
  •           h) A Complete ActiveX Web Control Tutorial – CodeProject

 

    四、根证书自动安装问题

          如果数字签名证书是从VeriSign等机构购买的,一般不存在根证书问题,因为微软的IE已经默认将VeriSign设置为受信任的根证书机构了:

         image

      但如果是从CA365这类不是很权威的机构申请的证书,由于这些机构的根证书默认并不在IE的“受信任的根证书颁发机构”名单里,所以在使用时需要先在用户电脑上将CA365的根证书手动导入进去。其实,12306.cn网站的根证书就是自己给自己颁发的,所以其网站上就明文提示需要自动安装根证书:

       image

 

      对于绝大部分用户来说,对什么什么叫数字签名什么叫根证书之类的概念是一头雾水的,让他们自己安装根证书感觉有点憋屈。如果能让IE自己自动安装根证书就好了。理论上是可以自动安装的,但由于权限的问题实际用起来不是那么爽,用户体验仍然很差。其原理就是利用微软的CAPICOM组件和xenrlinf组件对本地证书进行操作从而达到检测根证书是否存在以及安装证书的目的的,以下有两个文章对此做了描述:

      a) 将Capicom调用代码封装到ActiveX——解决javascript调Capicom读取数字证书信息时,IE弹出安全提示的问题

      b) 使用CAPICOM实现证书管理

    

     上海证券报社远程办公系统就使用了自动安装根证书的方法:

       http://ro.cnstock.com/

       image

 

      另外,在CA365上也提供了自动安装根证书的代码例子(如何在用户的客户机上自动安装根证书?):

       http://www.ca365.com/forward.do?pageurl=/ca/yhsc/11.jsp

      只不过这个例子在运行时一般会出错,以下语句总是创建对象失败:

           Set st = CreateObject("CAPICOM.Store")

      究其原因,还是浏览器对ActiveX的安全设置问题,降低IE对ActiveX的安全设置就可以了,但这个要求对于用户来说太麻烦了,还不然让用户自己一步一步将根证书导入系统中,或者自己包装一个安装程序,运行后自动导入根证书。 再见


0 0