?? 22.txt
字號:
WIN32 API
一、VB5.0與Windows API 間的呼叫技巧
一、VB5.0與Windows API 間的呼叫技巧
一般會使用WINDOW API的情況,實在是因為VB本身不提供某些功能,但是,程式所需又
不得不然,例如:讀取Registry內的資料,VB只提供SaveSetting、Getsetting 等系列
的指令,但是它只能讀取特定地區的值,要讀、刪、更動其他區域的值時,就無法使用
。再如:仔細看一看Combo Box的Events,其中沒有MouseMove,但這是我們經常用上的
一個Event,那該如何呢?是的,那只有透過Winodow API。而VB呼叫Window API一般不
都使用API檢視員,直接將相對應的API COPY到我們的程式中就好,那還用什麼技巧嗎?
其實不然,因為VB資料格式的問題,又加上VB本身沒有指標,在許多地方需要一些小技
巧才能解決,而且我們經常因應不同的需求,將API 檢視員的宣告COPY過來後再做一些
修改,最重要的,如果有一個.DLL檔,它不在API 檢視員中定義,那時,就只有自己想
辦法啦。
一、 整數叁數
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
Windows API 宣告
SHORT GetKeyState( int nVirtKey )
對應的VB宣告
Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
-----------------------------------------------------------------------------
這個API 可用來檢視某些KEY (如Insert鍵、Num Lock、CapsLock等)是on/off。程式如
下:這個例子應該可十分楚的看到各個整數間的宣告對應。
-----------------------------------------------------------------------------
Dim InsertMode as Integer
InsertMode = GetKeyState(vbKeyInsert) And vbShiftMask
If InsertMode = 1 then
Debug.print "表示 Insert Mode"
Else
Debug.print "表示 OverWrite Mode"
End If
-----------------------------------------------------------------------------
二、 指向整數的指標
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內定是使用傳址呼叫,所以ByRef 可以省略,也就是說
Func(ByRef param1 as type)
與
Func(param1 as type)
是相同的,使用傳址呼叫的方式,不外??想將叁數傳給API 後將結果傳回來。然而LONG
型態的傳址呼叫在VB中又占了相當大的份量,因為32位元的指標都是LONG的型態,而字
串、自定型態的Structure在Windows API中是以指標來傳遞的,而指標的傳遞事實上也
是Long值的傳遞,只不過傳過去的LONG值,於WIN API中會將之當成Address,而再配合
指標運作而得指標所指的內容,這個觀念在後面會很重要。
例如: -----------------------------------------------------------------------------
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
);
相對應的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 '//最後一個叁數是ByRef之宣告
-----------------------------------------------------------------------------
我們經常會想要用程式來讀取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字串傳遞時再一并介紹。
三、 字串叁數
凡是所有字串叁數指標都以 ByVal 叁數名稱 As String 傳。如RegOpenKeyEx()的第二叁
數ByVal lpSubKey As String,便是一例。或許會問,這個例子是把subkey值傳給 Win
API所以用ByVal,沒什麼大不了,其實不然,要Win API傳回字串時,也一定要用ByVal
的宣告。這是VB5字串格式(BSTR)與WIN API標準字串格式(LPSTR)不同的因素。 LPSTR
字串格式是NULL Terminate的字串,若有一字串"HaHa !OK!",則格式如下:
-----------------------------------------------------------------------------
Address 0 1 2 3 4 5 6 7 8 9
-- -- -- -- -- -- -- -- -- --
內容 H a H a ! O K ! \0
而BSTR則在字串的前面還有一個LONG值存字串長度,格式如下:
Address 0.. 3 4 5 6 7 8 9 10 11 12 13
------ -- -- -- -- -- -- -- -- -- --
內容 9 H a H a ! O K ! \0
-----------------------------------------------------------------------------
所以了字串以ByVal的方式來傳像不像指到BSTR中第4個位置,如此一來,不就和LP STR
可以相容了嗎?我想也正因為如此以ByVal的方式來傳String可以取得Win API的傳回值,
(就算不是如此,至少這麼想比較記得住String要用ByVal的方式傳)。現在又有一個問題,
Window95 API的字串使用的是ASCII Code但VB是用Unicode,Unicode占兩個位元組,那麼
能和WinAPI的字串相?所幸我們可以先不用管它,因為vb本身做了轉換,即 vb傳給api時,
轉了一次,傳回時又轉回 Unicode,所以如果我們用的是Byte Array來傳字串,也可以但
是要自己去轉碼。。然而32位元的VB 中,字串有種格式,一個是BSTR,另一個是HLSTR
,如果我們宣告的串是非固定長度者,就會是BSTR,反之則為HLSTR。
DIM BSTR5 AS STRING 劤 BSTR
DIM HLSTR5 AS STRING(255) 劤 HLSTR
VB5中WIN32 API的呼叫請多多使用BSTR,因為使用HLSTR的結果是,VB還得做HLSTR ->
BSTR的轉換來呼叫WIN API若有傳回STRING而後再做BSTR->HLSTR的工作。然而使用
BSTR來工作時,若處理有傳回值的STRING叁數,則還要有額外的動作:
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個數。
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) '設定初值
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
*****************************************************************************
以范例一的例子來看,設定lpString= String(255,0)的目的,是設定255個字元的空間
給lpString(加上最後的null一共256),CharCnt的值是12,明眼者可看到len("這是一個
test") 會是8,但CharCnt是12, 所以直接使用Left()函數來取得子字串會有問題,這
是UniCode與ANSI String間的關系,所以了,當您看到有些書的范例用這種方法取子字
串,是不太完善的,所以改用范例二的方式,比較正確。
范例二
*****************************************************************************
Form1.Caption = "這是一個test"
lpString = String(255, 0) '設定初值
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
-----------------------------------------------------------------------------
仔細看一下第三個叁數,WIN API中是LPDWORD可是VB中麼會是用ByVal的方式傳遞呢?原
因在於 lpReserved一定要傳Null進去,VB在呼叫時便在 這叁數的位置上填0(見范例三)。
為何傳Null就得這做?我們可以這麼想,我們 在程式中下指令,告訴VB要以 ByVal 的
方式傳0出去,而WIN API里,它可不管VB是ByVal或ByRef,API 認定我們傳進來的就是
它需要的,所以了,第三個叁數在API中認定我們傳進的是一個Address,而VB 傳0進去,
那代表API若去取得它的內容,便會取得Address 0 的內容,或許Window的 Null值便是
指向Address 0呢!另一個作法比較直接,將VB宣告的第三個叁數宣告由 ByVal
lpReserved As Long改成 ByVal lpReserved as String而使用時固定傳 vbNullString
進去也可以。這里在一個觀念,那就是VB對Win API的宣告,純粹是給VB 自己看的,在
API中定義了一個指標的叁數,Api檢視員會將之宣告成ByRef的方式(字串除外),但我們
可隨需要而更動它,一個原始應為ByRef的叁數宣告,我們可以將之改為 ByVal的方式,
只要我們能取得叁數的位址,而將這型態為Long的位址以ByVal傳出去, Win API 端根
本不知道VB端是用什麼方式傳,反正只要我們傳了一Long值進去,Win API 就會以這個
Long值當作是Address來運作。
問題還沒有解決,RegQueryValueEx()的第四個叁數lpType若為REG_SZ(= 1)那代表
lpData是Null Terminate的String,若為REG_DWORD ( = 4)那代表lpData是Long值,正
是因為沒有辦法事先知道lpData的真正型態,所以VB就使用 ASAny的型態,它要VB放棄
型態的檢查,傳什麼值進去都可以,但是在這里有一些問題,如果lpType是REG_DWORD
那麼lpData以ByRef的方式沒有問題,但是如果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內的ValueName
ValueName= "ProDuctId "
tp = REG_SZ
strBuff = String(255, 0)
leng1 = Len(strBuff) + 1
resul = RegQueryString(hkey, ValueName, 0, tp, strBuff, leng1)
'注意,第三個叁數傳0,leng1傳回copy 到strBuff的字元個數(anci)
leng1 = InStr(1, strBuff, Chr(0), vbBinaryCompare) '重新算個數(UniCode)
ResultStr = Left(StrBuff,leng1-1) '這便是ProductId的值
*****************************************************************************
在這里有另外一件事要特別說明,范例三程式中有一行leng1=Len(strBuffer)+1,這行
可省不得,很奇怪吧,為什麼明明是一個傳回值,卻一定要設定給它一個strBuff 的大
小呢?這是因為許多WIN API 不會聰明到找strBuff的Null Char在哪里,所以需要程式
傳進去,而後它再依這個欄位傳回填入strBuff 的數目。
五、Array叁數的傳遞
我們知道Win API 的陣列傳遞是傳陣列的起始位址,所以了,在VB中唯一要注意的是起
始位置的寫法。以另一個取得Window目錄所在路徑的API為
例: -----------------------------------------------------------------------------
UINT GetWindowsDirectory(
LPTSTR lpBuffer, // address of buffer for Windows directory
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -