1. 程式人生 > >瀏覽器外掛之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開發。

一、建立專案及新增介面

     在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控制元件是可以正常執行的了。~~~