VB與Windows API 間的呼叫技巧
發(fā)布時間:2008-08-06 閱讀數(shù): 次 來源:網(wǎng)樂原科技
一般會使用WINDOW API的情況,實在是因為VB本身不提供某些功能,但是,程式所
需又不得不然,例如:讀取Registry內(nèi)的資料,VB只提供SaveSetting、Getsetting 等
系列的指令,但是它只能讀取特定地區(qū)的值,要讀、刪、更動其他區(qū)域的值時,就無法
使用。再如:仔細(xì)看一看Combo Box的Events,其中沒有MouseMove,但這是我們經(jīng)常用
上的一個Event,那該如何呢?是的,那只有透過Winodow API。而VB呼叫Window API一
般不都使用API檢視員,直接將相對應(yīng)的API COPY到我們的程式中就好,那還用什麼技
巧嗎?其實不然,因為VB資料格式的問題,又加上VB本身沒有指標(biāo),在許多地方需要一
些小技巧才能解決,而且我們經(jīng)常因應(yīng)不同的需求,將API 檢視員的宣告COPY過來後再
做一些修改,最重要的,如果有一個.DLL檔,它不在API 檢視員中定義,那時,就只有
自己想辦法啦。
一、 整數(shù)參數(shù)
Windows API32位元VB
============================== =============================
Int, INT ByVal Long
UNIT, DWORD ByVal Long
BOOL ByVal Long ture時為1
WPARAM, LPARAM, LRESULT ByVal Long
Handle(如HKEY) ByVal Long
WORD, ATOM, SHORT ByVal Integer
BYTE, CHAR ByVal Byte
Eg.
-----------------------------------------------------------------------------
Windows API 宣告
SHORT GetKeyState( int nVirtKey )
對應(yīng)的VB宣告
Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
-----------------------------------------------------------------------------
這個API 可用來檢視某些KEY (如Insert鍵、Num Lock、CapsLock等)是on/off。程
式如下:這個例子應(yīng)該可十分楚的看到各個整數(shù)間的宣告對應(yīng)。
-----------------------------------------------------------------------------
Dim InsertMode as Integer
InsertMode = GetKeyState(vbKeyInsert) And vbShiftMask
If InsertMode = 1 then
Debug.print "表示 Insert Mode"
Else
Debug.print "表示 OverWrite Mode"
End If
-----------------------------------------------------------------------------
二、 指向整數(shù)的指標(biāo)
Windows API 32位元VB
============================ ==========================
LPINT (ByRef ) Long
LPUNIT (ByRef ) Long
LPBOOL (ByRef ) Long
LPDWORD (ByRef ) Long
LPHANDLE (如:PHKEY) (ByRef ) Long
LPWORD (ByRef ) Integer
LPSHORT (ByRef ) Integer
LPBYTE (ByRef ) Byte
VB內(nèi)定是使用傳址呼叫,所以ByRef 可以省略,也就是說
Func(ByRef param1 as type)
與
Func(param1 as type)
是相同的,使用傳址呼叫的方式,不外乎想將參數(shù)傳給API 後將結(jié)果傳回來。然而LONG
型態(tài)的傳址呼叫在VB中又占了相當(dāng)大的份量,因為32位元的指標(biāo)都是LONG的型態(tài),而字
串、自定型態(tài)的Structure在Windows API中是以指標(biāo)來傳遞的,而指標(biāo)的傳遞事實上也
是Long值的傳遞,只不過傳過去的LONG值,於WIN API中會將之當(dāng)成Address,而再配合
指標(biāo)運作而得指標(biāo)所指的內(nèi)容,這個觀念在後面會很重要。
例如:
-----------------------------------------------------------------------------
LONG RegOpenKeyEx(
HKEY hKey, // handle of open key
LPCTSTR lpszSubKey, // address of name of subkey to open
DWORD dwReserved, // reserved
REGSAM samDesired, // security access mask
PHKEY phkResult // address of handle of open key
);
相對應(yīng)的VB 宣告
Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" _
(ByVal hKey As Long, _
ByVal lpSubKey As String, _
ByVal ulOptions As Long, _
ByVal samDesired As Long, _
phkResult As Long) As Long ’//最後一個參數(shù)是ByRef之宣告
-----------------------------------------------------------------------------
我們經(jīng)常會想要用程式來讀取Registry中的資料,例如:我們想得知Win95的Produ
ct ID該如何做呢?這里有幾個觀念要先清楚:首先:ProductId在何處呢?在
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVerson下的ProductId。
我們要取得的便是
KEY 為 HKEY_LOCAL_MACHINE
SUBKEY 為 SOFTWARE\Microsoft\Windows\CurrentVerson
ValueName 為 ProductId 的value
然而要取得ProductId的value可沒那麼直接,要先取得SubKey的KeyHandle而Key
Handle的取得便是利用RegQueryKeyEx的API 。程式部份在介紹Win API字串傳遞時再一
并介紹。
三、 字串參數(shù)
凡是所有字串參數(shù)指標(biāo)都以 ByVal 參數(shù)名稱 As String 傳。如RegOpenKeyEx()的
第二參數(shù) ByVal lpSubKey As String,便是一例。或許會問,這個例子是把subkey值傳
給 Win API所以用ByVal,沒什麼大不了,其實不然,要Win API傳回字串時,也一定要
用ByVal的宣告。這是VB5字串格式(BSTR)與WIN API標(biāo)準(zhǔn)字串格式(LPSTR)不同的因素。
LPSTR 字串格式是NULL Terminate的字串,若有一字串"HaHa !OK!",則格式如下:
-----------------------------------------------------------------------------
Address 0 1 2 3 4 5 6 7 8 9
-- -- -- -- -- -- -- -- -- --
內(nèi)容 H a H a ! O K ! \0
而BSTR則在字串的前面還有一個LONG值存字串長度,格式如下:
Address 0.. 3 4 5 6 7 8 9 10 11 12 13
------ -- -- -- -- -- -- -- -- -- --
內(nèi)容 9 H a H a ! O K ! \0
-----------------------------------------------------------------------------
所以了字串以ByVal的方式來傳像不像指到BSTR中第4個位置,如此一來,不就和LP
STR 可以相容了嗎?我想也正因為如此以ByVal的方式來傳String可以取得Win API的傳
回值,(就算不是如此,至少這麼想比較記得住String要用ByVal的方式傳)。現(xiàn)在又有一
個問題,Window95 API的字串使用的是ASCII Code但VB是用Unicode,Unicode占兩個位
元組,那麼能和WinAPI的字串相?所幸我們可以先不用管它,因為vb本身做了轉(zhuǎn)換,即
vb傳給api時,轉(zhuǎn)了一次,傳回時又轉(zhuǎn)回 Unicode,所以如果我們用的是Byte Array來
傳字串,也可以但是要自己去轉(zhuǎn)碼。
。然而32位元的VB 中,字串有種格式,一個是BSTR,另一個是HLSTR,如果我們宣告的
串是非固定長度者,就會是BSTR,反之則為HLSTR。
DIM BSTR5 AS STRING BSTR
DIM HLSTR5 AS STRING(255) HLSTR
VB5中WIN32 API的呼叫請多多使用BSTR,因為使用HLSTR的結(jié)果是,VB還得做HLSTR
-> BSTR的轉(zhuǎn)換來呼叫WIN API若有傳回STRING而後再做BSTR->HLSTR的工作。然而使用
BSTR來工作時,若處理有傳回值的STRING參數(shù),則還要有額外的動作:
1.先給定字串的初值,且字串的長度要夠放傳回值。
2.傳回後,去除傳回值中多余的字元。
或
例如:
-----------------------------------------------------------------------------
int GetWindowText(
HWND hWnd, // handle of window or control with text
LPTSTR lpString, // address of buffer for text
int nMaxCount // maximum number of characters to copy
);
該 API 取得WINDOW Title Bar的文字,而傳回值是放入lpString的character個數(shù)。
VB的宣告如下:
Decl are Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hwnd As Long, _
ByVal lpString As String, _
ByVal cch As Long) As Long
范例一
*****************************************************************************
Dim CharCnt As Long
Dim lpString As String
Dim tmpstr As String
Dim NullPos As Long
Form1.Caption = "這是一個test"
lpString = String(255, 0) ’設(shè)定初值
CharCnt = GetWindowText(Me.hwnd, lpString, 256) ’CharCnt = 12
tmpstr = Left(lpString, CharCnt) ’如此做會有一些問題
Debug.Print Len(tmpstr) ’得12
Label1.Caption = Left(lpString, CharCnt)
Debug.Print Len(Label1.Caption) ’得8
*****************************************************************************
以范例一的例子來看,設(shè)定lpString= String(255,0)的目的,是設(shè)定255個字元的
空間給 lpString(加上最後的null一共256),CharCnt的值是12,明眼者可看到len("這
是一個test") 會是8,但CharCnt是12, 所以直接使用Left()函數(shù)來取得子字串會有問
題,這是UniCode與ANSI String間的關(guān)系,所以了,當(dāng)您看到有些書的范例用這種方法
取子字串,是不太完善的,所以改用范例二的方式,比較正確。
范例二
*****************************************************************************
Form1.Caption = "這是一個test"
lpString = String(255, 0) ’設(shè)定初值
CharCnt = GetWindowText(Me.hwnd, lpString, 256) ’CharCnt = 12
NullPos = InStr(1, lpString, Chr(0), vbBinaryCompare)
tmpstr = Left(lpString, NullPos - 1)
lable1.Caption = tmpstr
*****************************************************************************
四、 Null 值的傳遞
我們再回到求ProductId的問題,我們已知使用RegOpenKeyEx()來取得subkey的Han
dle值,緊接著便是用RegQueryValueEx()來取值。
-----------------------------------------------------------------------------
LONG RegQueryValueEx(
HKEY hKey, // handle of key to query
LPTSTR lpszValueName, // address of name of value to query
LPDWORD lpdwReserved, // reserved
LPDWORD lpdwType, // address of buffer for value type
LPBYTE lpbData, // address of data buffer
LPDWORD lpcbData // address of data buffer size
);
VB的宣告(由API檢視員中Copy下來者)
Declare Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" _
(ByVal hKey As Long, _
ByVal lpValueName As String, _
ByVal lpReserved As Long, _
lpType As Long, _
lpData As Any, _
lpcbData As Long) As Long
-----------------------------------------------------------------------------
仔細(xì)看一下第三個參數(shù),WIN API中是LPDWORD可是VB中麼會是用ByVal的方式傳遞
呢?原因在於 lpReserved一定要傳Null進去,VB在呼叫時便在 這參數(shù)的位置上填0(見
范例三)。為何傳Null就得這做?我們可以這麼想,我們 在程式中下指令,告訴VB要以
ByVal 的方式傳0出去,而WIN API里,它可不管VB是ByVal或ByRef,API 認(rèn)定我們傳
進
來的就是它需要的,所以了,第三個參數(shù)在API中認(rèn)定我們傳進的是一個Address,而VB
傳0進去,那代表API若去取得它的內(nèi)容,便會取得Address 0 的內(nèi)容,或許Window的
Null值便是指向Address 0呢!另一個作法比較直接,將VB宣告的第三個參數(shù)宣告由
ByVal lpReserved As Long改成 ByVal lpReserved as String而使用時固定傳
vbNullString 進去也可以。這里在一個觀念,那就是VB對Win API的宣告,純粹是給VB
自己看的,在API中定義了一個指標(biāo)的參數(shù),Api檢視員會將之宣告成ByRef的方式(字串
除外),但我們可隨需要而更動它,一個原始應(yīng)為ByRef的參數(shù)宣告,我們可以將之改為
ByVal的方式,只要我們能取得參數(shù)的位址,而將這型態(tài)為Long的位址以ByVal傳出去,
Win API 端根本不知道VB端是用什麼方式傳,反正只要我們傳了一Long值進去,Win API
就會以這個Long值當(dāng)作是Address來運作。
問題還沒有解決,RegQueryValueEx()的第四個參數(shù)lpType若為REG_SZ(= 1)那代表
lpData是Null Terminate的String,若為REG_DWORD ( = 4)那代表lpData是Long值,
正
是因為沒有辦法事先知道lpData的真正型態(tài),所以VB就使用 ASAny的型態(tài),它要VB放棄
型態(tài)的檢查,傳什麼值進去都可以,但是在這里有一些問題,如果lpType是REG_DWORD
那麼lpData以ByRef的方式?jīng)]有問題,但是如果lpType 是REG_SZ,STRING是要以ByVal
的方式來宣告,所以會有沖突,而解決的方式就是改寫API檢視員Copy進來的宣告。
-----------------------------------------------------------------------------
Declare Function RegQueryLong Lib "advapi32.dll" Alias "RegQueryValueExA" _
(ByVal hKey As Long, _
ByVal lpValueName As String, _
ByVal lpReserved As Long, _
lpType As Long, _
lpData As Long, _
lpcbData As Long) As Long
Declare Function RegQueryString Lib "advapi32.dll" Alias "RegQueryValueExA" _
(ByVal hKey As Long, _
ByVal lpValueName As String, _
ByVal lpReserved As Long, _
lpType As Long, _
Byval lpData As String, _
lpcbData As Long) As Long
-----------------------------------------------------------------------------
使用兩個宣告來解決這個問題,依不同的lpType呼叫不同的函式,即lpType= REG_
DWORD時,呼叫RegQueryLong, lpType = REG_SZ時則為RegQueryString這也可以讓我們
了解為何VB API的宣告為什麼要有Alias的存在。
范例三
*****************************************************************************
Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) _
As Long
Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA"
(ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, _
ByVal samDesired As Long, phkResult As Long) As Long
Declare Function RegQueryString Lib "advapi32.dll" Alias _
"RegQueryValueExA" (ByVal hKey As Long, _
ByVal lpValueName As String, ByVal lpReserved As Long, _
lpType As Long, ByVal lpData As String, lpcbData As Long) As Long
Const REG_EXPAND_SZ = 2
Const HKEY_CLASSES_ROOT = &H80000000
Const READ_CONTROL = &H20000
Const STANDARD_RIGHTS_READ = (READ_CONTROL)
Const KEY_QUERY_VALUE = &H1
Const KEY_ENUMERATE_SUB_KEYS = &H8
Const KEY_NOTIFY = &H10
Const SYNCHRONIZE = &H100000
Const KEY_READ = ((STANDARD_RIGHTS_READ Or _
KEY_QUERY_VALUE Or KEY_ENUMERATE_SUB_KEYS Or _
KEY_NOTIFY) And (Not SYNCHRONIZE))
Dim key5 As String, ValueName as String, strBuff as String, ResultStr as String
Dim leng1 As Long, resul As Long, hkey As Long
Dim tp As Long, i As Long
key5 = " SOFTWARE\Microsoft\Windows\CurrentVerson "
resul = RegOpenKeyEx(HKEY_CLASSES_ROOT, key5, 0, KEY_READ, hkey)
’hkey便是subkey (key5)的KeyHandle,先取得它才能存取Subkey內(nèi)的ValueName
ValueName= "ProDuctId "
tp = REG_SZ
strBuff = String(255, 0)
leng1 = Len(strBuff) + 1
resul = RegQueryString(hkey, ValueName, 0, tp, strBuff, leng1)
’注意,第三個參數(shù)傳0,leng1傳回copy 到strBuff的字元個數(shù)(anci)
leng1 = InStr(1, strBuff, Chr(0), vbBinaryCompare) ’重新算個數(shù)(UniCode)
ResultStr = Left(StrBuff,leng1-1) ’這便是ProductId的值
*****************************************************************************
在這里有另外一件事要特別說明,范例三程式中有一行l(wèi)eng1=Len(strBuffer)+1,
這行可省不得,很奇怪吧,為什麼明明是一個傳回值,卻一定要設(shè)定給它一個strBuff
的大小呢?這是因為許多WIN API 不會聰明到找strBuff的Null Char在哪里,所以需要
程式傳進去,而後它再依這個欄位傳回填入strBuff 的數(shù)目。
五、Array參數(shù)的傳遞
我們知道Win API 的陣列傳遞是傳陣列的起始位址,所以了,在VB中唯一要注意的
是起始位置的寫法。以另一個取得Window目錄所在路徑的API為
例:
-----------------------------------------------------------------------------
UINT GetWindowsDirectory(
LPTSTR lpBuffer, // address of buffer for Windows directory
UINT uSize // size of directory buffer
); // 若成功,則傳回目錄的字元數(shù)
VB的宣告(API檢視員)
Declare Function GetWindowsDirectory Lib "kernel32" Alias _
"GetWindowsDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long) _
As Long
我們將之更改為
Declare Function GetWindowsDirectory Lib "kernel32" Alias _
"GetWindowsDirectoryA" ( lpBuffer As Byte, ByVal nSize As Long) As Long