瀏覽器外掛之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專案:
點選“OK”後將彈出如下對話方塊:
依次點選“Next”按鈕直到“Control Settings”標籤頁:
由於本例子只演示僅提供函式介面不基於介面的ActiveX,故“Create control based on”選擇“(none)”即可。點選"Finish”按鈕,即完成了專案的建立,檔案結構如下:
右擊專案名稱,選擇“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中新增我們需要與外部互動的介面方法和屬性了。選擇“Class View”,右擊“MyTestActiveXLib->_DMyTestActiveX”,在彈出的選單中可以選擇Add Function或Add Property來新增介面方法或介面屬性:
這裡以定義一個LONG AddFun(LONG num1,LONG num2) 的介面函式為例,新增Menthod如下圖所示:
點選Finish後,即可在“MyTestActiveXCtrl.cpp”檔案找到剛新增的介面函式程式碼:
在函式體中完成自定義的業務邏輯即可。
二、實現安全介面
上述專案編譯後即可生成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的定義:
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檔案起始處還需要引入如下兩個檔案方能正常編譯:
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. 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”, 編輯資原始檔資訊並確保以下幾個專案的正確性:
a) BLOCK的值為“040904e4”
b) OLESelfRegister的值為“\0”
c) VarFileInfo中的Translation後對應為“0x0409, 1252”
到目前為止,可以編譯專案,輸出的ocx控制元件是可以正常執行的了。~~~