瀏覽器插件之ActiveX開發(一)

原文鏈接:http://www.cnblogs.com/qguohog/archive/2013/01/22/2871805.html

     一般的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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章