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