如何讓VB調用外部的DLL

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

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章