zoukankan      html  css  js  c++  java
  • 浏览器插件之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
    // 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;
        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 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.
     
        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;
    }

      : 很多例子里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开发参考资料:

    1. A Complete ActiveX Web Control Tutorial 
    2. Enable an MFC ActiveX Control to Self-Register

    ATL ActiveX开发参考资料:

    1. 洞庭散人: COM组件开发实践(二)

    综合参考资料:

    1. 杨峰- COM组件与设计应用
    2. COM开发推荐书籍(洞庭散人推荐)

    =======================================================================
    野文(Jasson Qian)
    ------------------------------------------------------
    博客园:http://qguohog.cnblogs.com
    CSDN:http://blog.csdn.net/sallay
  • 相关阅读:
    Spark Scala 读取GBK文件的方法
    Mac OS X 系统下自带的文本文件格式转换工具iconv
    报到
    java 字符串中含有双引号" "与单引号' '问题
    div1嵌套div2,div2居中的解决办法
    ionic4 创建 angular项目 ReactNative下载第三方库出错解决Error: EPERM: operation not permitted, rename
    ionic+cordova 创建项目+打包
    jxl读取excel文件异常:Unable to recognize OLE stream 的解决方法
    学习 javascript (一)javascript 简介
    学习 JavaScript (四)核心概念:操作符
  • 原文地址:https://www.cnblogs.com/qguohog/p/2871805.html
Copyright © 2011-2022 走看看