VB如何調(diào)用DLL
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
Windows動(dòng)態(tài)連接庫是包含數(shù)據(jù)和函數(shù)的模塊,可以被其它可執(zhí)行文件(EXE、DLL、OCX 等)調(diào)用。動(dòng)態(tài)連接庫包含兩種函數(shù):輸出(exported)函數(shù)和內(nèi)部(internal)函數(shù)。輸出函數(shù)可以被其它模塊調(diào)用,而內(nèi)部函數(shù)則只能在動(dòng)態(tài)連接庫內(nèi)部使用。盡管動(dòng)態(tài)連接庫也能輸出 數(shù)據(jù),但實(shí)際上它的數(shù)據(jù)通常是只在內(nèi)部使用的。使用動(dòng)態(tài)連接庫的優(yōu)點(diǎn)是顯而易見的。將應(yīng)用程序的一部分功能提取出來做成動(dòng)態(tài)連接庫,不但減小了主應(yīng)用程序的大小,提高了程序 運(yùn)行效率,還使它更加易于升級(jí)。多個(gè)應(yīng)用程序共享一個(gè)動(dòng)態(tài)連接庫還能有效地節(jié)省系統(tǒng)資源。正因?yàn)槿绱耍赪indows系統(tǒng)中,動(dòng)態(tài)連接庫得到了大量的使用。 一般來說,動(dòng)態(tài)連接庫都是以DLL為擴(kuò)展名的文件,如Kernel32.dll、commdlg.dll等。但也有例外,如16位Windows的核心部件之一GDI.exe其實(shí)也是一個(gè)動(dòng)態(tài)庫。編寫動(dòng)態(tài)連接庫的工具很多,如VisualC++、BorlandC++、Delphi等,具體方法可以參見相關(guān)文檔。下面只以Visual C++6.0為例,介紹一下開發(fā)應(yīng)用于VisualBasic6.0的動(dòng)態(tài)連接庫時(shí)應(yīng)注意的問題(本文中所有涉及C/C++語言或編譯環(huán)境的地方,都以VC為例;所有涉及Visual Basic的地方都以VB 為例)。 作為一種32位Windows應(yīng)用程序的開發(fā)工具,VB生成的exe文件自然也都是32位的,通常情況下也只能調(diào)用32位的動(dòng)態(tài)連接庫。但是,并不是所有的32位動(dòng)態(tài)庫都能被VB生成的exe 文件正確地識(shí)別。一般來說,自己編寫用于VB應(yīng)用程序調(diào)用的動(dòng)態(tài)連接庫時(shí),應(yīng)注意以下幾個(gè)方面的 問題: 1、生成動(dòng)態(tài)庫時(shí)要使用__stdcall調(diào)用約定,而不能使用缺省的__cdecl調(diào)用約定;__stdcall 約定通常用于32位API函數(shù)的調(diào)用。 2、在VC中的定義文件(.def)中,必須列出輸出函數(shù)的函數(shù)名,以強(qiáng)制VC系統(tǒng)將輸出函數(shù)的裝飾名(decoratedname)改成普通函數(shù)名;所謂裝飾名是VC的編譯器在編譯過程中生成的輸出函數(shù)名,它包含了用戶定義的函數(shù)名、函數(shù)參數(shù)及函數(shù)所在的類等多方面的信息。由于在VC中定義文件不是必需的,因此工程不包含定義文件時(shí)VC就按自己的約定將用戶定義的輸出函數(shù)名修改成裝飾名后放到輸出函數(shù)列表中,這樣的輸出函數(shù)在VB生成的應(yīng)用程序中是不能正確調(diào)用的(除非聲明時(shí)使用Alias子句)。因此需要增加一個(gè).def文件,其中列出用戶需要的函數(shù)名,以強(qiáng)制VC不按裝飾名進(jìn)行輸出。 3、VC中的編譯選項(xiàng)"結(jié)構(gòu)成員對(duì)齊方式(structure member alignment)" 應(yīng)設(shè)成4字節(jié),其原因?qū)⒃诤笪脑敿?xì)介紹。 4、由于在C中整型變量是4個(gè)字節(jié),而VB中的整型變量依然只有2個(gè)字節(jié),因此在C中聲 明的整型(int)變量在VB中調(diào)用時(shí)要聲明為長(zhǎng)整型(long),而C中的短整型(short)在VB中則 要聲明成整型(integer);下表針對(duì)最常用的C語言數(shù)據(jù)類型列出了與之等價(jià)的Visual Basic 類型(用于32位版本的Windows)。 C語言數(shù)據(jù)類型在VisualBasic中聲明為調(diào)用時(shí)使用的表達(dá)式 ATOM ByVal variable As Integer 結(jié)果為Integer 類型的表達(dá)式 BOOL ByVal variable As Long 結(jié)果為 Long 類型的表達(dá)式 BYTE ByVal variable As Byte 結(jié)果為 Byte 類型的表達(dá)式 CHAR ByVal variable As Byte 結(jié)果為 Byte 類型的表達(dá)式 COLORREF ByVal variable As Long 結(jié)果為 Long 類型的表達(dá)式 DWORD ByVal variable As Long 結(jié)果為 Long 類型的表達(dá)式 HWND, HDC, HMENU ByVal variable As Long 結(jié)果為 Long 類型的表達(dá)式等Windows 句柄 INT, UINT ByVal variable As Long 結(jié)果為 Long 類型的表達(dá)式 LONG ByVal variable As Long 結(jié)果為 Long 類型的表達(dá)式 LPARAM ByVal variable As Long 結(jié)果為 Long 類型的表達(dá)式 LPDWORD variable As Long 結(jié)果為 Long 類型的表達(dá)式 LPINT, LPUINT variable As Long 結(jié)果為 Long 類型的表達(dá)式 LPRECT variable As type 自定義類型的任意變量 LPSTR, LPCSTR ByVal variable As String 結(jié)果為 String 類型的表達(dá)式 LPVOID variable As Any 任何變量(在傳遞字符串的時(shí)候使用ByVal) LPWORD variable As Integer 結(jié)果為Integer 類型的表達(dá)式 LRESULT ByVal variable As Long 結(jié)果為 Long 類型的表達(dá)式 NULL As Any 或 ByVal Nothing 或 ByVal variable As Long ByVal 0& 或 VBNullString SHORT ByVal variable As Integer 結(jié)果為Integer 類型的表達(dá)式 VOID Sub procedure 不可用 WORD ByVal variable As Integer 結(jié)果為Integer 類型的表達(dá)式 WPARAM ByVal variable As Long 結(jié)果為 Long 類型的表達(dá)式 5、VB中進(jìn)行32位動(dòng)態(tài)庫的聲明時(shí),函數(shù)名是大小寫敏感的。在獲得了需要的動(dòng)態(tài)連接 庫之后,就可以在VB中進(jìn)行調(diào)用了。但是,由于VB不能驗(yàn)證應(yīng)用程序傳遞到動(dòng)態(tài)連接庫中的參 數(shù)值是否正確,因此VB程序中大量的API調(diào)用可能會(huì)降低整個(gè)應(yīng)用程序的穩(wěn)定性,也會(huì)增加以 后維護(hù)的難度。所以,決定在VB程序中直接調(diào)用API函數(shù)時(shí)要慎重,但適當(dāng)?shù)氖褂肁PI調(diào)用確實(shí) 能夠有效地提高VB程序的性能。這之間的平衡需要編程人員根據(jù)實(shí)際情況來掌握。下面就具體介紹一下在VB中調(diào)用API函數(shù)時(shí)需要做的工作。 要聲明一個(gè)DLL過程,首先需要在代碼窗口的"通用(General)"部分增加一個(gè)Declare語句。如果該過程返回一個(gè)值,應(yīng)將其聲明為 Function: Declare Function publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])] As Type 如果過程沒有返回值,可將其聲明為Sub: Declare Sub publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])] 缺省情況下,在標(biāo)準(zhǔn)模塊中聲明的DLL過程,可以在應(yīng)用程序的任何地方調(diào)用它。在其它類型的模塊中定義的DLL過程則是模塊私有的,必須在它們前面聲明Private關(guān)鍵字,以示區(qū)分。下面分別介紹聲明語句的各個(gè)組成部分。 (一)、指定動(dòng)態(tài)庫: Declare語句中的Lib子句用來告訴Visual Basic如何找到包含過程的.dll文件。 如果引用的過程屬于Windows核心庫(User32、Kernel32或GDI32),則可以不包含文件擴(kuò)展名,如: Declare Function GetTickCount Lib "kernel32" Alias "GetTickCount" () As Long 對(duì)于其它動(dòng)態(tài)連接庫,可以在Lib子句指定文件的路徑: Declare Function lzCopy Lib "c:/windows/lzexpand.dll" _ (ByVal S As Integer, ByVal D As Integer) As Long 如果未指定libname的路徑,Visual Basic將按照下列順序查找該文件: ?、?exe文件所在的目錄 ?、诋?dāng)前目錄 ③Windows系統(tǒng)目錄 ?、躓indows目錄 ⑤Path環(huán)境變量中的目錄 下表中列出了常用的操作系統(tǒng)環(huán)境庫文件。 動(dòng)態(tài)鏈接庫描述 Advapi32.dll高級(jí)API服務(wù),支持大量的API(其中包括許多安全與注冊(cè)方面的調(diào)用) Comdlg32.dll通用對(duì)話框API庫 Gdi32.dll圖形設(shè)備接口API庫 Kernel32.dllWindows32位核心的API支持 Lz32.dll32位壓縮例程 Mpr.dll多接口路由器庫 Netapi32.dll32位網(wǎng)絡(luò)API庫 Shell32.dll32位ShellAPI庫 User32.dll用戶接口例程庫 Version.dll版本庫 Winmm.dllWindows多媒體庫 Winspool.drv后臺(tái)打印接口,包含后臺(tái)打印API調(diào)用。 對(duì)于Windows的系統(tǒng)API函數(shù),可以利用VB提供的工具API Viewer查找某一函數(shù)及其相 關(guān)數(shù)據(jù)結(jié)構(gòu)和常數(shù)的聲明,并復(fù)制到自己的程序中。 (二)、使用別名: A.函數(shù)名是標(biāo)準(zhǔn)的名稱 Declare語句中的Alias子句是一個(gè)可選的部分,用戶可以通過它所標(biāo)識(shí)的別名對(duì)動(dòng)態(tài) 庫中的函數(shù)進(jìn)行引用。例如,在下面的語句中,聲明 了一個(gè)在VB中名為MyFunction的函數(shù),而它在動(dòng)態(tài)庫Mydll.dll中最初的名字是MyFunctionX。 Private Declare Function MyFunction Lib "Mydll.dll" _ Alias "MyFunctionX" ( ) As Long 需要注意的是,Alias子句中的函數(shù)名是大小寫敏感的,也就是說,必須與函數(shù)在生成時(shí)的聲明(如在C源文件中的聲明)一致。這是因?yàn)?2位動(dòng)態(tài)庫與16位動(dòng)態(tài)庫不同,其中的函數(shù)名是區(qū)分大小寫的。同樣道理,如果沒有使用Alias子句,那么在Function(或Sub)后的函數(shù)名也是區(qū)分大小寫的。 通常在以下幾種情況時(shí)需要使用Alias子句: A.處理使用字符串的系統(tǒng)Windows API過程 如果調(diào)用的系統(tǒng)Windows API過程要使用字符串,那么聲明語句中必須增加一個(gè)Alias 子句,以指定正確的字符集。包含字符串的系統(tǒng)Windows API函數(shù)實(shí)際有兩種格式:ANSI和Unicode( 關(guān)于ANSI和Unicode兩種字符集的區(qū)別將在后面詳細(xì)闡述)。因此,在Windows頭文件中,每個(gè)包含字符串的函數(shù)都同時(shí)有ANSI版本和Unicode版本。例如,下面是SetWindowText函數(shù) 的兩種C語言描述??梢钥吹?,第一個(gè)描述將函數(shù)定義為SetWindowTextA,尾部的"A" 表明它是一個(gè)ANSI函數(shù): WINUSERAPI BOOL WINAPI SetWindowTextA(HWND hWnd, LPCSTR lpString); 第二個(gè)描述將它定義為 SetWindowTextW, 尾部的"W" 表明它是一個(gè) Unicode 函數(shù): WINUSERAPI BOOL WINAPI SetWindowTextW(HWND hWnd, LPCWSTR lpString); 因?yàn)閮蓚€(gè)函數(shù)實(shí)際的名稱都不是"SetWindowText",要引用正確的函數(shù)就必 須增加一個(gè)Alias子句: Private Declare Function SetWindowText Lib "user32" _ Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal _ lpString As String) As Long 應(yīng)當(dāng)注意,對(duì)于VB中使用的系統(tǒng)WindowsAPI函數(shù),應(yīng)該指定函數(shù)的ANSI版本,因?yàn)橹?有WindowsNT才支持Unicode版本,而Windows95不支持這個(gè)版本。僅當(dāng)應(yīng)用程序只運(yùn)行 在WindowsNT平臺(tái)上的時(shí)候才可以使用Unicode版本。 B.函數(shù)名是不標(biāo)準(zhǔn)的名稱 有時(shí),個(gè)別的DLL過程的名稱不是有效的標(biāo)識(shí)符。例如,它可能包含了非法的字符(如連 字符),或者名稱是VB的關(guān)鍵字(如GetObject)。在這種情況下,可以使用Alias關(guān)鍵字。例 如,操作環(huán)境DLLs中的某些過程名以下劃線開始。盡管在VB標(biāo)識(shí)符中允許使用標(biāo)識(shí)符,但是下劃線不能作為標(biāo)識(shí)符的第一個(gè)字符。為了使用這種過程,必須先聲明一個(gè)名稱合法的過程, 然后用Alias子句引用過程的真實(shí)名稱: Declare Function lopen Lib "kernel32" Alias "_lopen" _ (ByVal lpPathName As String, ByVal iReadWrite _ As Long) As Long 在上例中,lopen是VB中使用的過程名稱。而_lopen則是動(dòng)態(tài)連接庫中可以識(shí)別的名 稱。 C.使用序號(hào)標(biāo)識(shí)DLL過程 除了使用名稱之外,還可以使用序號(hào)來標(biāo)識(shí)DLL過程。某些動(dòng)態(tài)連接庫中不包含過程的名稱,在聲明它們包含的過程時(shí)必須使用序號(hào)。同使用名稱標(biāo)識(shí)的DLL過程相比,如果使用序號(hào),在最終的應(yīng)用程序中消耗的內(nèi)存將比較少,而且速度會(huì)快些。但是,一個(gè)具體的API的序號(hào) 在不同的操作系統(tǒng)中可能是不同的。例如GetWindowsDirectory在Win95下的序號(hào)為432,而在WindowsNT4.0下為338??偠灾?,如果希望應(yīng)用程序能夠在不同的操作系統(tǒng)下運(yùn)行,那么最好不要使用序號(hào)來標(biāo)識(shí)API過程。如果過程不屬于API,或者應(yīng)用程序使用的范圍很有 限,那么使用序號(hào)還是有好處的。 要使用序號(hào)來聲明DLL過程,Alias子句中的字符串需要包含過程的序號(hào),并在序號(hào)的 前面加一個(gè)數(shù)字標(biāo)記字符(#)。例如,Windowskernel中的GetWindowsDirectory函數(shù)的序 號(hào)為432;可以用下面的語句來聲明該DLL過程: Declare Function GetWindowsDirectory Lib "kernel32" _ Alias "#432" (ByVal lpBuffer As String, _ ByVal nSize As Long) As Long 在這里,可以使用任意的合法名稱作為過程的名稱,VB將用序號(hào)在DLL中尋找過程。 為了得到要聲明的過程的序號(hào),可以使用Dumpbin.exe等實(shí)用工具(Dumpbin.exe是Microsoft VisualC++提供的一個(gè)實(shí)用工具,它的使用說明可以參見VC的文檔)。利用Dumpbin,可以提取出.dll文件中的各種信息,例如DLL中的函數(shù)列表,它們的序號(hào)以及與代碼有關(guān)的其它信息。 (三)、使用值或引用傳遞 在缺省的情況下,VB以引用方式傳遞所有參數(shù)(ByRef)。這意味著并沒有傳遞實(shí)際的參 數(shù)值,VB只傳遞了數(shù)據(jù)的32位地址。另外有許多DLL過程要求參數(shù)以值方式傳遞(ByVal)。這意味著它們需要實(shí)際的數(shù)據(jù),而不是數(shù)據(jù)的內(nèi)存地址。如果過程需要一個(gè)傳值參數(shù),而傳遞給它的參數(shù)是一個(gè)指針,那么由于得到了錯(cuò)誤的數(shù)據(jù),該過程將不能正確地工作。要使參數(shù)以使用值方式傳遞,在Declare語句中需要在參數(shù)聲明的前面加上ByVal關(guān)鍵字。例如InvertRect過程要求第一個(gè)參數(shù)用傳值方式傳遞,而第二個(gè)用引用方式傳遞: Declare Function InvertRect Lib "user32" Alias _ "InvertRectA" (ByVal hdc As Long, lpRect As RECT) As Long 動(dòng)態(tài)連接庫的參數(shù)傳遞是一個(gè)復(fù)雜的問題,也是VB中調(diào)用動(dòng)態(tài)連接庫時(shí)最容易出現(xiàn)錯(cuò)誤的地方。參數(shù)類型或傳遞方式的聲明錯(cuò)誤都可能導(dǎo)致應(yīng)用程序出現(xiàn)GPF(通用保護(hù)錯(cuò)誤),甚至使操作系統(tǒng)崩潰,因此我們將在后面專門詳細(xì)地討論這個(gè)問題。 (四)、靈活的參數(shù)類型 某些DLL過程的同一個(gè)參數(shù)能夠接受多種數(shù)據(jù)類型。如果需要傳遞多種類型的數(shù)據(jù),可以將參數(shù)聲明為AsAny,從而取消類型限制。例如,下面的聲明中的第三個(gè)參數(shù)(lpptAsAny) 既可以傳遞一個(gè)POINT結(jié)構(gòu)的數(shù)組,也可以傳遞一個(gè)RECT結(jié)構(gòu): Declare Function MapWindowPoints Lib "user32" Alias _ "MapWindowPoints" (ByVal hwndFrom As Long, _ ByVal hwndTo As Long, lppt As Any, _ ByVal cPoints As Long) As Long As Any子句提供了一定的靈活性,但是,由于它不進(jìn)行任何的類型檢查,風(fēng)險(xiǎn)也隨之增加。因此在使用AsAny子句時(shí),必須仔細(xì)檢查所有參數(shù)的類型。正確的函數(shù)聲明是在VB中調(diào)用動(dòng)態(tài)連接庫的前提,但要想在VB中用對(duì)、用好動(dòng)態(tài)庫中的 函數(shù),僅僅有聲明還是遠(yuǎn)遠(yuǎn)不夠的。前面已經(jīng)說過,由于VB不能驗(yàn)證應(yīng)用程序傳遞到動(dòng)態(tài)連接 庫中的參數(shù)值是否正確,因此就要求程序員應(yīng)對(duì)參數(shù)類型有非常詳細(xì)的了解,否則很容易引起應(yīng)用程序發(fā)生通用保護(hù)錯(cuò)或?qū)е聺撛诘腂ug,降低軟件的可靠性。下面將參數(shù)類型分為簡(jiǎn)單數(shù)據(jù)類型、字符串、和用戶自定義類型三種分別進(jìn)行討論。 (1)、簡(jiǎn)單數(shù)據(jù)類型: 簡(jiǎn)單數(shù)據(jù)類型是指Numeric數(shù)據(jù)類型(包括Integer、Long、Single、Double、Currency類型)、Byte數(shù)據(jù)類型和Boolean數(shù)據(jù)類型。它們的共同的特點(diǎn)是結(jié)構(gòu)簡(jiǎn)單,操作系統(tǒng)在處理時(shí)不必進(jìn)行特殊的轉(zhuǎn)換。 簡(jiǎn)單數(shù)據(jù)類型參數(shù)的傳遞比較簡(jiǎn)單。我們知道,在VB中傳遞參數(shù)的方式有兩種:傳值(Byval) 和傳址(ByRef),缺省的方式是傳址。所謂傳值,就是對(duì)一個(gè)變量的具體值進(jìn)行傳遞;而傳址則 是傳遞變量的地址。例如,在VB程序中需要將一個(gè)整型變量m=10的值傳進(jìn)動(dòng)態(tài)庫,如果用傳值 方式,那么傳進(jìn)動(dòng)態(tài)庫的值就是10,而在傳址方式下,傳入的則是變量m的地址,相當(dāng)于C/C++ 中&m的值。需要注意的是,以傳值方式傳進(jìn)動(dòng)態(tài)連接庫的變量,其值在動(dòng)態(tài)庫中是不能 被改變的;如果需要在動(dòng)態(tài)連接庫中修改傳入?yún)?shù)的值,則必須使用傳址方式。一般來說,在VB 和動(dòng)態(tài)連接庫之間傳遞單個(gè)的簡(jiǎn)單數(shù)據(jù)類型,只要注意了以上幾個(gè)方面就可以了。當(dāng)需要將 一個(gè)簡(jiǎn)單數(shù)據(jù)類型的整個(gè)數(shù)組傳進(jìn)動(dòng)態(tài)庫時(shí),必須將相應(yīng)參數(shù)聲明為傳址方式,然后把數(shù)組 的第一個(gè)元素作為參數(shù)傳入,這樣在動(dòng)態(tài)連接庫中就得到了數(shù)組的首地址,從而可以對(duì)整個(gè)數(shù)組進(jìn)行訪問。例如,聲明了一個(gè)名為ReadArray的DLL過程,要求傳入一個(gè)整型數(shù)組aArray: Declare Function ReadArray Lib "mydll.dll" _ (aArray As Integer) As Integer 在調(diào)用時(shí)可以采用如下方式: Dim ret,I(5) as Integer … … ret = ReadArray(I(0)) ' 將整個(gè)數(shù)組傳入動(dòng)態(tài)連接庫 (2)、字符串參數(shù)的傳遞: 與簡(jiǎn)單數(shù)據(jù)類型相比,字符串類型(String、String*n)的參數(shù)傳遞要復(fù)雜得多,這主要是Windows 95 API和VB使用的字符串類型不同的緣故。VB使用被稱為BSTR的String數(shù)據(jù)類型,它是由自動(dòng)化(以前被稱為OLE Automation)定義的數(shù)據(jù)類型。一個(gè)BSTR由頭部和字符串組成,頭部包含了字符串的長(zhǎng)度信息,字符串中可以包含嵌入的null值。大部分的BSTR是 Unicode的,即每個(gè)字符需要兩個(gè)字節(jié)。BSTR通常以兩字節(jié)的兩個(gè)null字符結(jié)束。下圖表示 了一個(gè)BSTR類型的字符串。 (前綴)aTest/0 頭部BSTR指向數(shù)據(jù)的第一個(gè)字節(jié) 另一方面,大部分的DLL過程(包括Windows 95 API中的所有過程)使用LPSTR類型字符串,這是指向標(biāo)準(zhǔn)的以null結(jié)束的C語言字符串的指針,它也被稱為ASCIIZ字符串。LPSTR 沒有前綴。下圖顯示了一個(gè)指向ASCIIZ字符串的LPSTR。 aTest/0 LPSTR指向一個(gè)以null結(jié)尾的字符串?dāng)?shù)據(jù)的第一個(gè)字節(jié) 如果DLL過程需要一個(gè)LPSTR(指向以null結(jié)束的字符串的指針)作為參數(shù),可以在VB 中將一個(gè)字符串以傳值的方式傳遞給它。因?yàn)橹赶駼STR的指針實(shí)際指向以null值結(jié)束的字符串的第一個(gè)數(shù)據(jù)字節(jié),所以對(duì)于DLL過程來說,它就是一個(gè)LPSTR。這樣傳入動(dòng)態(tài)連接庫的字符串,DLL過程也可以對(duì)它進(jìn)行修改,盡管它是以傳值方式傳入的。只有當(dāng)DLL過程需要一個(gè)指向LPSTR的指針時(shí),才以傳址的方式傳入字符串,這時(shí)DLL過程得到的是一個(gè)指向字符串指針的指針(相當(dāng)于C/C++中的char**),而不是通常所用的字符串的首地址(相當(dāng)于C/C++中的char*)。 當(dāng)需要把一個(gè)字符串?dāng)?shù)組整個(gè)傳入動(dòng)態(tài)連接庫時(shí),情況就變得復(fù)雜多了,用傳遞簡(jiǎn)單數(shù)據(jù)類型數(shù)組的方式來傳遞字符串?dāng)?shù)組是行不通的。當(dāng)我們以傳值的方式將一個(gè)字符串?dāng)?shù)組的第一個(gè)元素傳進(jìn)動(dòng)態(tài)連接庫時(shí),DLL過程得到的實(shí)際上是該元素壓入堆棧段后的地址,而不是數(shù)據(jù)段中整個(gè)數(shù)組的首地址。也就是說,這時(shí)DLL過程只能得到數(shù)組的第一個(gè)元素,而無法訪問整個(gè)數(shù)組。而以傳址方式傳入第一個(gè)元素時(shí),DLL過程只能得到指向該元素在堆棧段中地址的指針,同樣無法訪問整個(gè)數(shù)組。這不能不說是VB的一個(gè)不足。因此,在程序設(shè)計(jì)中,如果確實(shí)需要將整個(gè)字符串?dāng)?shù)組傳入動(dòng)態(tài)庫,就必須采取其它方法。 我們知道,在VB中,有一種Byte數(shù)據(jù)類型。每個(gè)Byte型變量占一個(gè)字節(jié),不含符號(hào)位,因 此所能表示的范圍為0到255。這種數(shù)據(jù)類型是專門用于存放二進(jìn)制數(shù)據(jù)的。為了將整個(gè)字符 串?dāng)?shù)組傳進(jìn)動(dòng)態(tài)庫,可以用字節(jié)數(shù)組來保存字符串。由于Byte是一種簡(jiǎn)單數(shù)據(jù)類型,因此字節(jié) 數(shù)組的傳遞是非常簡(jiǎn)單的。首先,需要把一個(gè)字符串正確地轉(zhuǎn)變成一個(gè)字節(jié)數(shù)組。這要涉及一 些字符集的知識(shí)。Windows 95和VB使用不同的字符集,Windows 95 API使用的是ANSI或DBCS 字符集,而VB使用的則是Unicode字符集。所謂ANSI字符集,是指每個(gè)字符都用一個(gè)字節(jié)表示, 因此最多只能有28=256個(gè)不同的字符,這對(duì)于英語來說已經(jīng)足夠了,但不能完全支持其它語 言。DBCS字符集支持很多不同的東亞語言,如漢語、日語和朝鮮語,它使用數(shù)字0-255表示ASCII 字符,其它大于255或小于0的數(shù)字表明該字符屬于非拉丁字符集;在DBCS中,ASCII字符的長(zhǎng) 度是一個(gè)字節(jié),而漢語、日語和其它東亞字符的長(zhǎng)度是2個(gè)字節(jié)。而Unicode字符集則完全用 兩個(gè)字節(jié)表示一個(gè)字符,因此最多可以表示216=65536個(gè)不同字符。也就是說,ANSI字符集中 所有的字符都只占一個(gè)字節(jié),DBCS字符集中ASCII字符占一個(gè)字節(jié),漢字占兩個(gè)字節(jié),Unicode 字符集中每個(gè)字符都占兩個(gè)字節(jié)。由于VB與WindowsAPI使用的字符集不同,因此在進(jìn)行字符 串到字節(jié)數(shù)組的轉(zhuǎn)換時(shí),當(dāng)用Asc函數(shù)取得一個(gè)字符的字節(jié)碼后,需要判斷它是否是一個(gè)ASCII 字符;如果是ASCII字符,則在轉(zhuǎn)換后的字節(jié)數(shù)組中就只占一個(gè)字節(jié),否則要占兩個(gè)字節(jié)。 下面給出了轉(zhuǎn)換函數(shù):GetChar Byte得到一個(gè)字符的高字節(jié)或低字節(jié),它的第一個(gè)參數(shù) 是一個(gè)字符的ASCII碼,第二個(gè)參數(shù)是標(biāo)志取高字節(jié)還是低字節(jié);StrToByte按DBCS或ANSI格 式將一個(gè)字符串轉(zhuǎn)換成一個(gè)字節(jié)數(shù)組,第一個(gè)參數(shù)是待轉(zhuǎn)換的字符串,第二個(gè)參數(shù)是轉(zhuǎn)換后的一個(gè)定長(zhǎng)字節(jié)數(shù)組,若該數(shù)組長(zhǎng)度不足以存放整個(gè)字符串,則截去超長(zhǎng)的部分;ChangeStrAryToByte 利用前兩個(gè)函數(shù)將字符串?dāng)?shù)組轉(zhuǎn)換成字節(jié)數(shù)組,第一個(gè)參數(shù)是定長(zhǎng)的字符串?dāng)?shù)組,其中每個(gè)元素都是一個(gè)字符串(各個(gè)元素包含的字符數(shù)可以不同),第二個(gè)參數(shù)是一個(gè)變長(zhǎng)的字節(jié)數(shù)組, 保存轉(zhuǎn)換后的結(jié)果。 當(dāng)轉(zhuǎn)換完成以后,查看字節(jié)數(shù)組ResultAry,其中包含了21個(gè)元素,依次是:178,226,202,212,49,0,178,226,202,212,50,50,50,0,178,226,202,212,51,51,0。其中,[178,226]是"測(cè)"的字節(jié)碼,[202,112]是"試"的字節(jié)碼,49,50,51 分別為字符1、2、3的ASCII碼??梢姡?jīng)過轉(zhuǎn)換后,字符串?dāng)?shù)組中的各個(gè)元素按順序放在了字節(jié)數(shù)組中,相互間以終止符0分隔。 這樣,字符串?dāng)?shù)組就全部轉(zhuǎn)換成了字節(jié)數(shù)組,然后只要將字節(jié)數(shù)組的第一個(gè)元素以傳址的方式傳入動(dòng)態(tài)連接庫,DLL過程就可以正確地訪問數(shù)組中的所有字符串了。但是,使用這種方法,當(dāng)DLL過程處理結(jié)束返回VB時(shí),VB得到的仍然是字節(jié)數(shù)組。如果需要在VB中再次得到該字節(jié)數(shù)組表示的字符串,還要把整個(gè)字節(jié)數(shù)組重新以0為分割符分成多個(gè)子數(shù)組(每個(gè)子數(shù)組都對(duì)應(yīng)原來字符串?dāng)?shù)組中的一個(gè)元素),然后使用VB函數(shù)StrConv將每個(gè)子數(shù)組轉(zhuǎn)換成字符串(轉(zhuǎn)換時(shí)第二個(gè)參數(shù)選vbUnicode),就可以顯示或進(jìn)行其它操作了。例如,其中一個(gè)子數(shù)組的名字是SubAry,則函數(shù)StrConv(SubAry,vbUnicode)就返回了它所對(duì)應(yīng)的字符串。 總之,VB應(yīng)用程序和動(dòng)態(tài)庫間字符串參數(shù)的傳遞是一個(gè)比較復(fù)雜的過程,使用時(shí)要非常謹(jǐn)慎。同時(shí)應(yīng)盡可能避免傳遞字符串?dāng)?shù)組類型的參數(shù),因?yàn)檫@很容易引起下標(biāo)越界、堆棧溢出等嚴(yán)重錯(cuò)誤。 (3)、用戶自定義類型(User-defined Type)參數(shù)的傳遞 用戶自定義類型在VB中是一種重要的數(shù)據(jù)類型,它為編程者提供了很大的靈活性,使開發(fā)人員可以根據(jù)需要構(gòu)造自己的數(shù)據(jù)結(jié)構(gòu)。它相當(dāng)于C/C++中的結(jié)構(gòu)類型(structure)。在VB中,允許程序員以傳址的方式將自定義數(shù)據(jù)類型參數(shù)傳入動(dòng)態(tài)庫,DLL過程也可以將修改后的參數(shù)返回VB程序。但是,在VB中仍然不支持以傳值的方式傳遞用戶自定義類型參數(shù)。 傳遞用戶自定義類型參數(shù)時(shí),必須確保VB中的數(shù)據(jù)類型的成員與動(dòng)態(tài)庫中的結(jié)構(gòu)成員是一一對(duì)應(yīng)的,所占空間也必須嚴(yán)格一致。這里所說的一一對(duì)應(yīng),不僅是指VB 中的所有結(jié)構(gòu)成員在動(dòng)態(tài)庫的結(jié)構(gòu)中都必須有對(duì)應(yīng)的元素,而且它們?cè)跀?shù)據(jù)結(jié)構(gòu)中定義的順序也必須嚴(yán)格一致,這是VB中使用的"數(shù)據(jù)結(jié)構(gòu)成員對(duì)齊方式"決定的。在VB 中,數(shù)據(jù)結(jié)構(gòu)使用雙字對(duì)齊方式(4-byte alignment),因此,在用戶自己生成用于VB 調(diào)用的動(dòng)態(tài)連接庫時(shí),也必須把編譯選項(xiàng)"structure member alignment" 設(shè)為4字節(jié)(如前文所述)。 所謂結(jié)構(gòu)成員對(duì)齊方式是指一個(gè)數(shù)據(jù)結(jié)構(gòu)內(nèi)部,其成員的排列方式。譬如,在VB中,其對(duì)齊方式是4字節(jié),這就好象在一個(gè)數(shù)據(jù)結(jié)構(gòu)內(nèi)部分成了很多個(gè)4字節(jié)大小的小單元,如果相鄰 兩個(gè)或多個(gè)數(shù)據(jù)成員的大小可以放在一個(gè)單元中,那么就放在一起;否則這些小單元中可能 會(huì)出現(xiàn)未用的空字節(jié)。我們來看下面一個(gè)數(shù)據(jù)類型: Type TestType m1 as Integer m2 as Byte m3 as Long End Type 它的三個(gè)成員的大小加起來是2+1+4=7。但是,由于m1和m2的字節(jié)總長(zhǎng)度是3,小于4,它 們就存放于一個(gè)單元中;但該單元剩下的一個(gè)字節(jié)不足以放下一個(gè)Long型的成員m3,于是m3 就被放在下一個(gè)單元中,它們之間就有了一個(gè)未用的空字節(jié);因此,整個(gè)結(jié)構(gòu)所占實(shí)際長(zhǎng)度是8 字節(jié)。同理,如果將m3和m2的位置交換一下,它所占的尺寸就變成了9字節(jié)??梢?,成員在結(jié)構(gòu) 中的聲明順序也是非常重要的。 通常,當(dāng)一個(gè)用戶自定義類型中不包含字符串時(shí),向動(dòng)態(tài)連接庫中傳遞該類型的參數(shù)是沒有什么問題的。如果只傳遞一個(gè)自定義類型變量,則既可以傳遞該變量名,也可以傳遞該變 量的第一個(gè)成員,它們的效果是一樣的,都是將該變量的地址傳進(jìn)了動(dòng)態(tài)庫;同樣,如果要傳遞一個(gè)自定義類型的數(shù)組,則既可以傳遞該數(shù)組的第一個(gè)元素,也可以傳遞第一個(gè)元素的第一個(gè)成員。但是,如果用戶自定義類型中包含字符串類型時(shí),又該如何與動(dòng)態(tài)連接庫傳遞參數(shù)呢?答案是令人遺憾的:在VB中,你無法將一個(gè)包含字符串成員的用戶自定義類型變量或數(shù) 組安全、正確地傳入動(dòng)態(tài)庫中。如果你這樣做了,即使某次僥幸得到了正確的結(jié)果,在其背后也隱藏著許多致命的危險(xiǎn)。因此,如果一定要在用戶自定義類型中包含字符串變量,并且該類型的變量又要作為參數(shù)傳入動(dòng)態(tài)庫時(shí),你最好修改類型定義,把其中的字符串成員用相應(yīng)的字節(jié)數(shù)組類型替換掉(轉(zhuǎn)換方法可參見前文),這樣就可以在VB 和動(dòng)態(tài)庫間傳遞這種類型的參數(shù)了。 另外,在VB 中還可以把一個(gè)函數(shù)的指針傳遞到動(dòng)態(tài)庫中,方法也并不復(fù)雜。但筆者強(qiáng)烈建議最好不要這么做,因?yàn)檫@樣一來VB 應(yīng)用程序就幾乎完全喪失了它所應(yīng)有的安全性。如果 確實(shí)需要傳遞函數(shù)指針的話,那么還是編一個(gè)C/C++ 的程序來完成這項(xiàng)工作吧。 總之,在VB中調(diào)用DLL過程是一個(gè)比較復(fù)雜的問題,編程人員必須很好地把握,才能達(dá)到既提高了程序效率,開拓了程序功能,又不降低程序安全性的目的。另外需要特別指出的一點(diǎn)是,在本文中提到的所有動(dòng)態(tài)連接庫,都是指沒有使用自動(dòng)化(OLE Automation)技術(shù)的動(dòng)態(tài)庫,Windows API和大多數(shù)用戶自編的動(dòng)態(tài)連接庫都是這種類型的。對(duì)于使用了OLE Automation技術(shù)的動(dòng)態(tài)連接庫,其參數(shù)傳遞的方式有所不同,讀者可以參閱有關(guān)OLE 技術(shù)的書籍,在此不再涉及。 該文章在 2013/12/27 21:43:33 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |