用ATL開發和部署ActiveX網頁控件
Posted on 2007-08-01 10:53 北極燕鷗
摘 要 ActiveX插件技術廣泛的運用於B/S系統中,本文通過一個項目實例,詳細介紹用ATL開發和部署ActiveX網頁控件的過程。學習使用ActiveX讓瀏覽器訪問客戶端的硬件資源。
關鍵字 ATL,ActiveX控件,COM組件
一、前言
在B/S結構的系統中,出於安全性考慮一般不准許瀏覽器訪問客戶端的硬件資源,如控制打印機,照相機等。對於一個完善系統來說,往往很多時候又需要控制這些資源。通過在瀏覽器中插入ActiveX插件是一種很好的解決方式。
在實際的項目開發中,遇到系統登錄需要增加物理身份識別。即在系統登錄的時候,除了要驗證用戶名和密碼外,還需要驗證硬件USB KEY上的信息。具體業務流程爲:客戶端程序讀取用戶硬件USB KEY裏的個人信息(即加密認證信息),提交給認證服務器進行認證,認證服務器通過身份識別後,業務系統通過解析返回的XML信息判斷用戶是否合法有效,建立起用戶和業務系統的信任通道。讀取硬件USB KEY的信息我們通過本例的ActiveX控件來完成。硬件USB KEY選用飛天誠信的ePass1000ND產品。
二、概念
1、ActiveX控件
ActiveX是Microsoft提出的一組使用COM(Component Object Model,組件對象模型)使得軟件組件在網絡環境中進行交互的技術集。它與具體的編程語言無關。作爲針對Internet應用開發的技術,ActiveX被廣泛應用於WEB服務器以及客戶端的各個方面。
ActiveX是從Microsoft的複合文檔技術—OLE成長起來的。其基本的出發點是想讓某個軟件通過一個通用的機構爲另一個軟件提供服務,可以將其插入到WEB網頁或其它應用程序中。在Internet上的使用,ActiveX特點是:一般軟件需要用戶單獨下載然後執行安裝,而ActiveX插件是當用戶瀏覽到特定的網頁時,IE瀏覽器即可自動下載並提示用戶安裝。 但安裝的一個前提是必須經過用戶的同意及確認。
2、COM技術
COM是Microsoft組件對象模型的簡稱。是一個說明如何建立可動態交替更新組件的規範。它提供了客戶和組件爲保證能夠互操作應該遵循的標準。該標準對於組件架構的重要性同其他任何一個具有可交替更新部分的系統是一樣的。
COM標準包括規範和實現兩大部分,規範部分定義了組件和組件之間通信的機制,這些規範不依賴於任何特定的語言和操作系統,只要按照該規範,任何語言都可以使用;COM標準的實現部分是COM庫,COM庫爲COM規範的具體實現提供了一些核心服務。
在COM模型中,對象本身對於客戶來說是不可見的,客戶請求服務時,只能通過接口進行。一般接口是不會改變的。
3、ATL技術
ATL(Active Template Library)是微軟的活動模板庫,是一個產生C++/COM代碼的框架,專門用於開發COM組件。ATL提供了小巧、高效、靈活的類,這些類爲創建可互操作的COM組件提供了基本的設施。ATL完全面向COM組件,其結構完全針對COM中的諸多規範。是編寫COM組件的快捷工具。
三、實現
1、項目
打開Visual Studio.Net 2005,建立一個解決方案或項目DeanUSBKey。在項目類型中選擇Visual C++下的ATL選項,在模板中選擇”ATL Project”,項目名爲DeanUSBKey。點擊確定,系統就在指定的目錄下建立了DeanUSBKey項目和解決方案。
點擊確定後,會出現建立ATL項目嚮導對話框,引導用戶快捷方便的建立ATL項目。點擊下一步,進入項目屬性設置對話框,如圖1所示。可以通過該對話框選擇是否屬性化和發佈方式等。
圖1 項目屬性設置對話框
具體選項說明如下:
Attributed 即屬性化,支持屬性化編程,是未來的發展方向,是IDL方案的一種替代方案。
Dynamic-link library(DLL) 即動態鏈接庫,表示建立一個 DLL 的組件程序。
Executable(EXE) 即可執行文件,表示建立一個 EXE 的組件程序。
Service(EXE) 即服務,表示建立一個系統服務組件程序,系統啓動後就會加載並執行的程序。
Allow merging of proxy/stub code 即允許合併代理/存根代碼,選擇該項表示把“代理/存根”代碼合併到組件程序中,否則需要單獨編譯,單獨註冊代理存根程序。
Support MFC 即支持 MFC,建議不要選擇,除非有特殊的原因,比如我們原來的程序是基於MFC的,我們的組件必須要MFC的支持。一般在寫 ATL 程序,不選擇該項。但是很多VC程序員對於MFC的數據集合類和字符串類依賴很大,建議採用STL中的相關類進行替代。具體替換方案:
1、std::string代替MFC中的CString;
2、std::vector代替MFC的數組類如CArray,CPtrArray等;
3、std::list替換MFC中的CList等列表類;
4、對於BSTR建議採用CComBSTR 類,或_bstr_t類,本例子中就會用到該類;
Support COM+1.0 支持事務處理的 COM+ 功能。
我們選擇如圖1所示的選項,點擊完成。ATL Project項目就生成好了,系統會在指定目錄下生成一系列文件,ReadMe.txt裏有各文件的文件說明。尤其要注意接口定義語言文件(DeanUSBKey.idl),它描述了對象的接口細節。
2、組件
在COM模型中,客戶請求服務時,是通過接口和組件進行交互的。現在還是一個空的ATL項目,還沒有任何組件。
添加組件,也就是添加ATL對象類。在DeanUSBKey項目上點擊右鍵,添加類,彈出對話框。在類別中選擇ATL。在模板中選擇“ATL Simple Object”即ATL簡單對象。點擊確定,出現建立組件嚮導。如圖2所示,在Short Name輸入組件名稱USBKey,其它內容系統會自動填寫。注意組件名稱不能和項目名稱重名。點擊下一步進入組件選項設置界面。如圖3所示
圖2 ATL簡單對象組件名字對話框
圖3 ATL簡單對象組件選項對話框
具體選項說明如下:
Threading model 即線程模型,COM中的線程,這是一個複雜的部分。我們選"單元"(Apartment),它代表當在線程中調用組件函數的時候,這些調用會排隊進行。如果想了解詳細細節可以參看《COM技術內幕》一書。
Interface 即接口,雙重(Dual),雙重接口表示在一個接口中,同時支持自定義接口和 IDispatch 接口。這個非常重要,爲了能夠使組件能夠在腳本中使用,必須選擇雙重接口選項。因爲腳本語言的解釋器只認識 IDispatch 接口。自定義接口(Custom),直接實現的是IUnknown接口。
Aggregation 即聚合,寫的組件,將來是否允許被其他人以聚合方式(有聚合和包容兩種方式)使用。Only(只能創建爲聚合),有點類似 C++或Java 中的不能直接創建實例的虛類,如果不是處於設計目的,一般這個選項不用。大多數情況下支持“聚合”,所以我們選擇“Yes”。
ISupportErrorInfo 是否支持豐富信息的錯誤處理接口。
Connection points 即連接點,是否支持連接點接口(事件、回調)。
IObjectWithSite 是否支持IE的調用。
我們選擇如圖3所示的選項,點擊完成。USBKey的組件建立完成。在生成的USBKey.cpp裏將是接口IUSBKey的實現。
3、接口方法
在類視圖中,IUSBKey接口上點擊鼠標右鍵。在添加項裏有添加方法和屬性,選擇添加方法。打開圖4所示的添加接口方法對話框。添加接口方法GetContent,並添加接口方法的參數。[in]表示參數方向是輸入;[out]表示參數方向是輸出;[out,retval]表示參數方向是輸出,同時可以作爲函數運算結果的返回值。一個函數中,可以有多個[in]、[out],但[retval]只能有一個,並且要和[out]組合後在最後一個位置。詳細的定義說明可以參考IDL的語法說明。
圖4 添加接口方法對話框
在USBKey.cpp文件裏添加函數GetContent的具體實現過程。核心代碼如下:
STDMETHODIMP CUSBKey::GetContent(LONG lFlags, BSTR* pUSBContent)
{
EPAS_STATUS retval;//狀態
EPAS_HANDLE epsHandle ; //EPAS句柄
// 創建設備句柄
retval = epas_CreateContext(&epsHandle,0,EPAS_API_VERSION);//調用EPAS的API函數訪問USB Key硬件
if (FT_SUCCESS != retval)
{return ReturnError(retval);}//返回相應的錯誤
// 打開設備
retval = epas_OpenDevice(epsHandle,lFlags,(void *)szAppID);
if (FT_SUCCESS != retval)
{return ReturnError(retval);}
//得到序列號
unsigned long sn[2] = {0};
retval = epas_GetProperty(epsHandle,EPAS_PROP_SERNUM,NULL,sn,sizeof(sn));
if (FT_SUCCESS != retval)
{return ReturnError(retval);}
char m_sn [8*1024+17]={0};
sprintf_s(m_sn, "%08X%08X", sn[1], sn[0]);//以16進制打印到字符串m_sn中
//得到加密字符串
//1、登錄
char s[80] = "1234";//登錄密碼
retval = epas_Verify(epsHandle,EPAS_VERIFY_USER_PIN,(unsigned char*)s,4);
if (FT_SUCCESS != retval){return ReturnError(retval);}
//2、打開文件
EPAS_FILEINFO epsFileInfo = {0};
unsigned long epsFileID = 0x1234;//文件編號
retval = epas_OpenFile(epsHandle,0,epsFileID,&epsFileInfo,sizeof(epsFileInfo));
if (FT_SUCCESS != retval){return ReturnError(retval); }
//3、讀取文件內容
unsigned long rLen = 0;
unsigned char rBuff[8*1024] = {0};
ZeroMemory(rBuff,8*1024);
retval = epas_Read(epsHandle,0,0,rBuff,epsFileInfo.ulFileSize,&rLen);
if (FT_SUCCESS != retval){return ReturnError(retval);}
//4、關閉文件
retval = epas_CloseFile(epsHandle);
//關閉設備,刪除Context
retval = epas_CloseDevice(epsHandle);
retval = epas_DeleteContext(epsHandle);
strcat_s(m_sn,(char*)rBuff);
*pUSBContent=_com_util::ConvertStringToBSTR((char *)r_sn);
return S_OK;
}
爲了能在函數中使用USBKey廠家提供的訪問函數和使用BSTR類,需在stdafx.h頭文件裏面引入相應的.h和.lib文件。如下:
#include "FT_ND_API.h"// ePass1000ND的接口頭文件
#include "comutil.h"
#pragma comment(lib, " FT_ND_API.lib")
#pragma comment(lib, "comsuppw.lib")
如果編譯通過,VS.Net IDE會打開窗口選擇執行控件的外部文件。選擇regsvr32。編譯成功後,組件會自動註冊。可以在系統組件服務裏面查看剛註冊的組件DeanUSBKey。
4、錯誤處理
COM方法通過返回HRESULT來報告錯誤,其他信息異常可以通過 IErrorInfo 接口提供給客戶端,這裏主要講述HRESULT返回COM方法錯誤。
HRESULT由一個 32 位代碼組成。分爲四部分,如下:
Field |
Severity |
Reserved |
Facility |
Code |
Bit(s) |
31 |
29-30 |
16-28 |
0-15 |
各字段說明:
Severity 字段是其中最重要的一個。當一個方法返回時若該字段被設置了值,就說明發生了一個錯誤。該字段使所有的 COM 錯誤代碼顯示爲負的十進制整數。
Reserved 字段目前是預留字段。
Facility 字段爲錯誤類別代碼,總共表示 8192 種錯誤,由一個集中的機構負責分配這些種類。
Code 字段提供了一個可容納 65536 個代碼的空間。具體的錯誤代碼就在該字段裏面體現。
在讀取USBKey信息時,捕獲的錯誤做處理,以COM錯誤的形式拋出。即把前16位改爲0x80FF,代碼如下:
LONG CUSBKey::ReturnError(LONG retval)
{ return 0x80FF0000+retval;}
5、實現IObjectSafety接口
ActiveX控件的編寫到此就可以結束了,但我們在瀏覽器使用改控件的過程中,經常都會彈出現在運行的腳本不安全的提示。如果給客戶使用,將會帶來極大不便。怎麼解決呢,可以通過實現IObjectSafety接口來解決。ATL 在類 IObjectSafetyImpl 中提供了此接口的實現。如果瀏覽器發現你的控件支持 IObjectSafety,就在導入控件之前調用 IObjectSafety::SetInterfaceSafetyOptions 方法來確保安全性腳本操作。就不會彈出提示對話框。
在USBKey.h文件裏繼承類列表的末尾(class ATL_NO_VTABLE CUSBKey)加入如下語句: public IObjectSafetyImpl<CUSBKey, INTERFACESAFE_FOR_UNTRUSTED_CALLER| INTERFACESAFE_FOR_UNTRUSTED_DATA>,
並在COM 映射裏添加一下行(黑體部分):
BEGIN_COM_MAP(CUSBKey)
COM_INTERFACE_ENTRY(IObjectSafety)
END_COM_MAP()
四、測試
在網頁裏通過腳本語言調用ActiveX控件DeanUSBKey。可以通過VBScript和JAVAScript來調用。爲了能在腳本語言裏使用控件接口,需知道接口的classid,可以查看接口的註冊表腳本文件USBKey.rgs找到classid值。調用代碼爲:
<object classid="clsid: <?XML:NAMESPACE PREFIX = ST1 />4F3320E4-4B66-4C85-8538-6E17699AAB46" id="Dean" name = "Dean" ></object>
<form id="form1" name="form1" method="post" action="">
<a href="#" οnclick="return CallUSB();">js調用ActiveX測試</a>
<input id="Write" name="Write" type="button" value="vb調用ActiveX測試" />
</form>
<SCRIPT LANGUAGE="JavaScript">
<!—
//JavaScript調用Demo
function CallUSB()
{
try {
var USBContent = Dean.GetContent(0);
alert(USBContent);
} catch (e) {
alert("錯誤號: " + e.number );
}
return false;
}
//-->
</SCRIPT>
<script language="VBScript" type="text/vbscript">
‘VBScript調用Demo
Sub Write_OnClick
On Error Resume Next
USBContent = Dean.GetContent(0)
MsgBox USBContent
MsgBox (err.number and &hff)
End Sub
</script>
五、部署
Internet軟件分發單位是“軟件包”,它由包含.INF文件或軟件分發.OSD文件(或兩者都包括)的.CAB文件所組成。一個分發單位也可以包含軟件組件,如ActiveX控件,DLL文件等。
1、Inf文件編寫
INF文件是一個文本文件,指定運行控件所需要下載或者呈交的文件(比如.DLL或者其它.OCX)。一個.INF文件就捆綁了.CAB壓縮文件所有的必須文件。 缺省情況下,與現有硬盤中文件版本號相同的文件不被下載。INF文件如下:
[version]
signature="$CHICAGO$"
AdvancedINF=2.0
[Add.Code]
FT_ND_API.dll=FT_ND_API.dll
DeanUSBKey.dll=DeanUSBKey.dll
[FT_ND_API.dll]
file-win32-x86=thiscab
DestDir=11
FileVersion=1,0,6,413
[DeanUSBKey.dll]
file-win32-x86=thiscab
RegisterServer=yes
clsid={4F3320E4-4B66-4C85-8538-6E17699AAB46}
DestDir=11
FileVersion=1,0,0,1
[RegisterFiles]
%11%/DeanUSBKey.dll
說明:
"thiscab" 是一個關鍵字,指包含該INF的CAB文件。也可以從網上下載所需要的DLL文件,只要指定一個HTTP 網址即可,如:
file-win32-x86=http://www.chengdujob.net/activex/DeanUSBKey.DLL
關鍵字"file-win32-x86"指定平臺是x86。
"FileVersion"文件版本號。
"DestDir"指的是裝載目錄或者文件的地址: 11指系統目錄 WINDOWS/SYSTEM32;10 指Windows 目錄。
2、Cab打包
Windows在系統目錄中自帶了CAB製作工具IExpress(/WINDOWS/system32/目錄下)。打開IExpress:
1)選擇“Create new Self Extraction Directive file”,點擊下一步。
2)選擇“Create compressed files only(ActiveX Installs)”,點擊下一步。
3)點擊Add,把文件添加(ft_nd_api.dll,DeanUSBKey.dll, duk_usbkey.inf)添加進去,點擊下一步。
4)點擊Browse,輸入.CAB文件的存放地址(包含所取文件名),這裏取TestCAB.CAB,並且要選中 “Store files using Long File Name inside Package”。點擊下一步。
5)選擇“Don’t save”,一直點擊下一步,直到完成。
3、自動安裝
用瀏覽器調用ActiveX組件或者發佈組件打包文件都需要用OBJECT元素。發佈.CAB文件,需要在OBJECT元素的CODEBASE特性引用包含.INF文件的.CAB文件。當訪問該頁面時,Internet Explorer將自動把.CAB文件作爲軟件分發單位下載並安裝,每次訪問時還會自動檢測版本並進行更新。注意,瀏覽器出於安全性考慮,會攔截未經數字認證的控件。修改瀏覽器設置,在Internet選項-〉安全-〉受信任的站點-〉站點 中添加服務器地址,不要選複選框“對該區域中的所有站點要求服務器驗證”。解決瀏覽器攔截問題,而不用更改瀏覽器的安全級別。
六、結束語
程序在Windows Server 2003+Microsoft Visual Studio.NET2005(C++)英文版環境下調試通過。組件技術得到越來越廣泛的應用,而VC提供的活動模板庫爲我們創建功能強大的COM組件提供了很好的框架。通過ActiveX網頁控件使網頁也可以訪問客戶端的硬件資源。豐富B/S結構系統的功能。