1. 程式人生 > >最全的基於MFC的ActiveX控制元件開發教程

最全的基於MFC的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

按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的定義:

         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函式,程式碼如下(照抄即可):

按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”, 編輯資原始檔資訊並確保以下幾個專案的正確性:

          image

        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,如下圖所示:

       image

      之所以註冊ocx時出錯,是因為註冊時找不到被呼叫的“mwhrf_bj.dll”檔案。將被呼叫的“mwhrf_bj.dll”檔案放在ocx檔案相同目錄下或者其他%PATH%路徑下(如Windows資料夾或System32資料夾等),則註冊ocx時不會報錯。在vs.net開發環境中可以直接將要被呼叫的外部dll檔案copy到Debug或Release目錄下即可,也可以在PreBuild腳本里將外部dll檔案COPY到編譯目標資料夾,如:

       image

     二、ActiveX的除錯方法

        在Vs.net 2008下可以對ActiveX按如下方式進行除錯:

        1、準備好Demo.html檔案並寫好測試程式,該頁面中需通過<object />來引用需測試的ocx控制元件(關於如何在html頁面中呼叫控制元件在後續文章將專門提及)。

        2、在vs.net 2008中右擊專案名稱,選擇“Properties”,在彈出框中的Debugging配置頁裡配置好CommandCommandArgs引數:

            Command :本地IE瀏覽器的路徑,如“ C:\Program Files\Internet Explorer\IEXPLORE.EXE

            Command Args :已經建立好的用於測試ocx的html檔案路徑(如上面提及的Demo.html檔案路徑)

            image

        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=”CHICAGO”表示這個.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檔案中最下方查詢;

                         image

           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值:

             image

     一般不建議手動修改程式中的這個uuid值,因為在xxxxCtrl.cpp檔案中也用到了這個id值,只是表現形式不一樣罷了:

            image

      控制元件註冊成功後,這個classid及控制元件檔案位置等資訊均寫入登錄檔了,如下所示:

            image

      當然,如果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值,如下圖:

       image

     即IMPLEMENT_OLECREATE_EX的第二引數就表示當前外掛的ProgId,該值可以根據實際需要自行修改。實際上,在登錄檔中通過ProgId是可以找到對應的ClassId的,兩者是有關聯的:

        image

      那麼如何判斷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的方式則呼叫外掛介面失敗。

  前面四篇文章都