儘管 Visual Basic 在 Win32api.txt 中提供了多組已定義好的宣告,但還是需要知道如何親自撰寫宣告。因為您以後可能要存取用其它語言所撰寫的 DLL 中的程序,或者覆寫 Visual Basic 已定義的宣告,以滿足特殊的需要。
要宣告一個 DLL 程序,請在程式碼視窗的「宣告」區加入一個 Declare 陳述式。如果該程序要傳回一個值,就將其宣告為 Function:
Declare Function publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])] As Type
如果程序 t 傳回值,可將其宣告為 Sub:
Declare Sub publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])]
標準模組中宣告的 DLL 程序是預設為公用的,可以在應用程式的任何地方呼叫它。但在其它型態模組中定義的 DLL 程序則是私有的,必須在它們前面加上 Private 關鍵字,以示區分。
在 32 位元的 Visual Basic 中,程序名稱是有分大小寫的,而在以前的 16 位元版本中,程序名稱則是不分大小寫的。
詳細資訊 請參閱《程式語言參考手冊》裡的〈Declare 陳述式〉。
指定程式庫
Declare 陳述式中的 Lib 子句會告訴 Visual Basic 到哪裡去尋找包含程序的 .dll 檔案。如果引用的程序屬於 Windows 核心程式庫 (User32、Kernel32 或 GDI32),則可以不需要加上副檔名:
Declare Function GetTickCount Lib "kernel32" Alias _
"GetTickCount" () As Long
對於其它 DLL,Lib 子句指定了檔案的路徑:
Declare Function lzCopy Lib "c:\windows\lzexpand.dll" _
(ByVal S As Integer, ByVal D As Integer) As Long
如果未指定 libname 的路徑,Visual Basic 將按照下列順序尋找檔案:
- .exe 檔案所在的目錄
- 目前目錄
- Windows 系統目錄 (通常為 \Windows\System)
- Windows 目錄 (不一定是 \Windows)
- Path 環境變數中的目錄
下表中列出了常用的作業環境程式庫檔案。
| 動態連結程式庫 |
描述 |
| Advapi32.dll |
進階 API 伺服程式庫,支援許多的 API 功能 (其中包括許多安全性與註冊方面的呼叫) |
| Comdlg32.dll |
常用對話方塊 API 程式庫 |
| Gdi32.dll |
圖形週邊設備介面 API 程式庫 |
| Kernel32.dll |
Windows 32 位元核心的 API 支援 |
| Lz32.dll |
32 位元壓縮常式 |
| Mpr.dll |
多介面路由器 (Multiple Provider Routerver) 程式庫 |
| Netapi32.dll |
32 位元網路 API 程式庫 |
| Shell32.dll |
32 位元 Shell API 程式庫 |
| User32.dll |
使用者介面常式程式庫 |
| Version.dll |
版本程式庫 |
| Winmm.dll |
Windows 多媒體程式庫 |
| Winspool.drv |
包含背景列印 API 呼叫的背景列印介面 (Print spooler interface)。 |
處理使用字串的 Windows API 程序
如果呼叫的 Windows API 程序要使用字串,那麼在宣告陳述式中,必須加上一個 Alias 子句,以指定正確的字集。包含字串的 Windows API 函數,實際上以兩種格式存在:ANSI 和 Unicode。因此,在 Windows 前置資料檔案 (header file) 中,每個包含字串的函數都同時會有 ANSI 版本和 Unicode 版本。
例如,以下是 SetWindowText 函數的兩種 C 語言描述。您可以看到,第一個描述將函數定義為 SetWindowTextA,尾部的「A」表明它是一個 ANSI 函數:
WINUSERAPI
BOOL
WINAPI
SetWindowTextA(
HWND hWnd,
LPCSTR lpString);
第二個描述將它定義為 SetWindowTextW,尾部的「W」表明它是一個 Unicode 函數:
WINUSERAPI
BOOL
WINAPI
SetWindowTextW(
HWND hWnd,
LPCWSTR lpString);
因為兩個函數實際的名稱都不是「SetWindowText」,要引用正確的函數就必須新增一個 Alias 子句:
Private Declare Function SetWindowText Lib "user32" _
Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal _
lpString As String) As Long
請注意,Alias 子句後面的字串必須是程序的真正名稱,而且大小寫必須一致。
重要事項 對於在 Visual Basic 中所使用的 API 函數,應該指定函數的 ANSI 版本,因為只有 Windows NT 才支援 Unicode 版本,而 Windows 95 是不支援的。當您確定您的應用程式只在 Windows NT 平台上執行時,才使用 Unicode 版本。
使用傳值或傳址方式傳遞引數
在預設情況下,Visual Basic 以傳址 (by reference) 方式,來傳遞所有的引數。這意味著 Visual Basic 並沒有傳遞實際的引數值,只傳遞了儲存資料的 32 位元位址。雖然在 Declare 陳述式中不需要包含 ByRef 關鍵字,但是如果包含該關鍵字,就能夠清楚地指定資料要以何種方式傳遞。
許多 DLL 程序要求傳遞引數的值 (by value)。這意味著它們需要實際的資料,而不是儲存資料的記憶體位址。如果在程序中,傳遞了一個指標給需要傳遞值的引數,那麼程序會得到錯誤的資料,因而無法正確地執行。
要傳遞值給引數的話,請在 Declare 陳述式中,宣告該引數的前面加上 ByVal 關鍵字。例如,InvertRect 程序要求第一個引數接收「值」,而第二個則接收「位址」:
Declare Function InvertRect Lib "user32" Alias _
"InvertRectA" (ByVal hdc As Long, _
lpRect As RECT) As Long
您也可以在呼叫程序時,使用 ByVal 關鍵字。
附註 當您檢視以 C 語言語法所撰寫的 DLL 程序文件時,請記住 C 是以值傳遞引數,除了陣列以外。
字串引數是個特例。如果以值傳遞字串,那麼傳遞的將是該字串中第一個資料位元組的位址;如果以傳址方式來傳遞字串,那麼實際傳遞的將是用來儲存另一個位址的記憶體的位址,其第二個「位址」實際是字串的第一個資料位元組的記憶體位址。本章後面的「將字串傳遞到 DLL 程序中」課題將說明如何決定字串引數傳遞的正確方式。
不標準的名稱
有時,個別的 DLL 程序名稱不是有效的識別符號。例如,它可能包含了不正確的字元 (如連字元),或者取了和 Visual Basic 的關鍵字相同 (如 GetObject) 的名稱。此時,可以使用 Alias 關鍵字來指明這些不合法的程序名稱。
例如,作業環境 DLL 中的某些程序名稱以底線開始,雖然 Visual Basic 的變數中允許使用底線字元,但是底線字元仍然無法作為變數的第一個字元。為了使用這種程序,必須先為函數宣告一個合法的名稱,然後用 Alias 子句來引用程序的真實名稱:
Declare Function lopen Lib "kernel32" Alias "_lopen" _
(ByVal lpPathName As String, ByVal iReadWrite _
As Long) As Long
在上例中,lopen 是 Visual Basic 中使用的程序名稱。而 _lopen 則是 DLL 中可以識別的名稱。
為了方便,您也可以使用 Alias 子句來改變程序的名稱。如果使用自己的名稱替代了程序原來的名稱 (例如使用 WinDir 替代 GetWindowsDirectoryA),那麼必須在文件中清楚地描述這種修改,如此將來才方便維護程式碼。
使用序號識別 DLL 程序
除了使用名稱之外,還可以使用指定了DLL 程序的序號 (ordinal number) 來識別 DLL 程序。有些 DLL 中不包含程序的名稱,在宣告這些程序時,就必須使用序號。比起使用名稱來識別 DLL 程序,使用序號的方法,在您完成的應用程式中,會消耗較少的記憶體,同時速度也稍快些。
重要事項 在不同的作業系統中,某些 特定 API 的序號可能會有所不同。例如 GetWindowsDirectory 在 Win95 下的序號為 432,而在 Windows NT 4.0 下為 338。總而言之,如果您希望應用程式能夠在不同的作業系統下執行,那麼就不要使用序號來識別 API 程序。不過,當程序不屬於 API,或者應用程式使用的範圍在您掌握之中,那麼使用序號還是有好處的。
要使用序號來宣告 DLL 程序,Alias 子句中的字串需要包含程序的序號,並在序號的前面加一個數字符號字元 (#)。例如,Windows kernel 中的 GetWindowsDirectory 函數的序號為 432,所以您可以使用以下的陳述式來宣告該 DLL 程序:
Declare Function GetWindowsDirectory Lib "kernel32" _
Alias "#432" (ByVal lpBuffer As String, _
ByVal nSize As Long) As Long
注意,這裡可以使用任意的合法名稱做為程序的名稱,因為 Visual Basic 將會使用序號在 DLL 中搜尋該程序。
欲取得想要宣告之程序的序號,可以使用 Dumpbin.exe 等公用程式。(Dumpbin.exe 是 Microsoft Visual C++ 所提供的一個公用程式。)利用 Dumpbin,可以讀取 .dll 檔案中的各種資訊,例如,列出包含在該DLL 中的函數及其序號,或其他與程式碼相關的資訊。
詳細資訊 關於執行 Dumpbin 公用程式的進一步資訊,請參閱 Microsoft Visual C++ 文件。
靈活的引數型態
在某些 DLL 程序中,同一個引數能夠接受多種的資料型態。如果需要傳遞多種型態的資料,可以將引數宣告為 As Any,以消除型態的限制。
例如,以下宣告中的第三個引數 (lppt As Any),即可視需要傳遞一個 POINT 結構的陣列,或傳遞一個 RECT 結構的陣列:
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 子句能夠提供靈活性,但是由於它不進行任何的型態檢查,所以風險也隨之增加。如果不進行型態檢查,那麼在呼叫程序時,使用錯誤型態的可能性也會增加,如此可能導致多種問題,包括讓應用程式失敗。因此,在使用 As Any 子句時,必須仔細地檢查所有引數的型態。
在消除了型態的限制以後,Visual Basic 會假設引數是以傳「址」方式傳遞。但如果要使用傳值的方式,可以加入 ByVal。至於字串,則會以傳值方式傳遞,因此傳遞的是指向字串的指標,而不是指向指標的指標。更深入的討論請參閱〈Passing Strings to a DLL Procedure〉的部份。
文章定位: