最全的基於MFC的ActiveX控制元件開發教程
瀏覽器外掛總體可以劃分為兩大陣營,即IE支援的外掛以及非IE支援的外掛。本來在Netscape時代,對於瀏覽器外掛是有公用的規範的(NPAPI),一開始所有瀏覽器都支援該規範,包括IE。後來出於商業原因,微軟的IE不再支援NPAPI,改而自己開發了一套基於COM的ActiveX體系,但這個體系對於非IE瀏覽器是拒絕支援的。所以目前的狀況基本是,IE瀏覽器僅支援ActiveX控制元件,而Firefox、Chrome等瀏覽器只支援另一類介面(XPCOM或NPAPI)。
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”進行配置。
注意:建立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
按Ctrl+C 複製程式碼 #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);按Ctrl+C 複製程式碼Cathelp.cpp
按Ctrl+C 複製程式碼#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; }按Ctrl+C 複製程式碼注: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函式,程式碼如下(照抄即可):
按Ctrl+C 複製程式碼 // 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; }按Ctrl+C 複製程式碼注:很多例子裡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控制元件是可以正常執行的了。~~~
按照上文《瀏覽器外掛之ActiveX開發(一)》的步驟,能開發一個基於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,如下圖所示:
之所以註冊ocx時出錯,是因為註冊時找不到被呼叫的“mwhrf_bj.dll”檔案。將被呼叫的“mwhrf_bj.dll”檔案放在ocx檔案相同目錄下或者其他%PATH%路徑下(如Windows資料夾或System32資料夾等),則註冊ocx時不會報錯。在vs.net開發環境中可以直接將要被呼叫的外部dll檔案copy到Debug或Release目錄下即可,也可以在PreBuild腳本里將外部dll檔案COPY到編譯目標資料夾,如:
二、ActiveX的除錯方法
在Vs.net 2008下可以對ActiveX按如下方式進行除錯:
1、準備好Demo.html檔案並寫好測試程式,該頁面中需通過<object />來引用需測試的ocx控制元件(關於如何在html頁面中呼叫控制元件在後續文章將專門提及)。
2、在vs.net 2008中右擊專案名稱,選擇“Properties”,在彈出框中的Debugging配置頁裡配置好Command和CommandArgs引數:
Command :本地IE瀏覽器的路徑,如“ C:\Program Files\Internet Explorer\IEXPLORE.EXE ”
Command Args :已經建立好的用於測試ocx的html檔案路徑(如上面提及的Demo.html檔案路徑)
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型別來進行轉換。下面幾篇參考文章均對此有所描述:
但實現起來也不是那麼容易,鑑於時間問題及我們實際需求的不迫切性,我對此沒有做過多嘗試。如果有成型例項,望請賜教。
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.ocx
mwhrf_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=thiscab
FileVersion=1,0,0,0
DestDir=10
a) signature=””表示這個.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檔案中最下方查詢;
FileVersion:檔案版本號。一般將ocx檔案的版本號視同為整個cab的版本號,在<object codebase=”xx.cab#version= 1,0,0,1 ”中將用到該版本號。
DestDir:該檔案需要COPY到目標機器的位置,11表示system32目錄下,10表示windows目錄下,……
有關inf檔案的具體內容可參考以下文章相關部分,已經非常詳細了:
注:如果考慮到終端使用者的許可權以及將ActiveX註冊到什麼位置(Current User或Machine),可參考
3、執行如下命令進行打包:
cabarc” -s 6144 N “xxxxxx.cab” “xxxxx.ocx” “mwhrf_bj.dll” “xxxxxx.inf”
其中凡是需要打包的檔案均要一一列出,inf檔案放在最後(未測試是否必須最後)。檔案路徑均可以是絕對路徑或相對路徑,不一定非得是相同資料夾下。
命令執行後將自動生成.cab檔案。
簡單總結一下前幾篇文章的內容,《瀏覽器外掛之ActiveX開發(一)》簡單介紹了一下如何在Vs.net 2008下用C++開發基於MFC的ActiveX外掛,《瀏覽器外掛之ActiveX開發(二)》介紹了開發外掛時可能遇到的問題,《瀏覽器外掛之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值:
一般不建議手動修改程式中的這個uuid值,因為在xxxxCtrl.cpp檔案中也用到了這個id值,只是表現形式不一樣罷了:
控制元件註冊成功後,這個classid及控制元件檔案位置等資訊均寫入登錄檔了,如下所示:
當然,如果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值,如下圖:
即IMPLEMENT_OLECREATE_EX的第二引數就表示當前外掛的ProgId,該值可以根據實際需要自行修改。實際上,在登錄檔中通過ProgId是可以找到對應的ClassId的,兩者是有關聯的:
那麼如何判斷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的方式則呼叫外掛介面失敗。
前面四篇文章都