三、 字串參數
凡是所有字串參數指標都以 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個位置,如此一來,不就和LPSTR 可以相容了嗎?我想也正因為如此以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 ?BR> DIM HLSTR5 AS STRING(255) ?BR>
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 LongForm1.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的Handle值,緊接著便是用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 LongDeclare 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)
C
凡是所有字串參數指標都以 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個位置,如此一來,不就和LPSTR 可以相容了嗎?我想也正因為如此以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 ?BR> DIM HLSTR5 AS STRING(255) ?BR>
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 LongForm1.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的Handle值,緊接著便是用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 LongDeclare 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)
C
解决方案 »
- VB窗体的简单问题
- 高手请出招:类模块中的方法如何引用?
- 控件数量的限制
- 高分求助vb数据库高手!解决问题!!!
- 使用 Install Shield Express 3.5.4 打包多个 VB Project, 安装后,所有的 Project 在一个 Group 中,结果 Icon 都变成一样的,怎么办?
- UCase(Trim(text1.text))是什么意思
- 怎样把TREEVIEW的内容存到文件中
- 如何获取屏幕中某一点的RGB值
- 用ACCESS设计了一报表,在VB中如何调用?
- 如何得到一个字符串是多少个象素的长度?不是len(string)!
- 关于控件问题!!!!!!
- 哪位大虾能告诉我哪儿有关于api函数的使用说明,特别是作图的,谢谢!
-----------------------------------------------------------------------------
UINT GetWindowsDirectory(
LPTSTR lpBuffer, // address of buffer for Windows directory
UINT uSize // size of directory buffer
); // 若成功,則傳回目錄的字元數
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-----------------------------------------------------------------------------
範例四*****************************************************************************
Dim n as Long
Dim Buff() as Byte
Dim StrA as StringBuff = space(256)
n=GetWindowsDirectory(Buff(0), 256)
Buff = Leftb(Buff, n)
StrA = StrConv(Buff, vbUniCode) 'StrA便是Windows所在目錄
***************************************************************************** 在範例四中,GetWindowsDirectory()傳入的第一個參數Buff(0)便是這陣列的起始Byte ,因VB 宣告成lpBuffer As Byte,故傳過去的是ByRef Buff(0)的位址,當然了,你也可以呼叫成n=GetWindowsDirectory(Buff(1), 256),只是傳回值是填在Buff(1) to Buff(n),而Buff(0)則仍為起始的Space Character(32),因為該API傳回值是字元個數,再加上存於Buff中的是Byte Array故,使用Leftb()去除多出的byte,再用StrConv將Byte Array轉成Unicode的字串。比照範例二的作法,我們也可以將ByteArray 改成以String的方式來做,二者可做一比較,誰比較好或比較順暢,那見人見智,不過可以肯定的是,如果傳的值是Binary的值,那麼使用Byte Array來做才對,因用String來傳的話,會經過轉換成UniCode的步驟,這中間會發生什麼事,沒人知道。
六、CallBack Function的作法 VB的使用者通常對於這個名詞有著多多少少的疑惑,或稱之為"哭爸"Function,而VB5使用手冊使用Window Procedure來說明,除非對Window 系統有一些了解,否則可能令人更不知所云;我使用另一個例子來說明,那便是KeyBoard Hook。什麼是KeyBoardHook 呢,簡言之便是按鍵盤時,便會自動執行某一段Function的功能,就好比Dos時代的攔截中斷向量一般。讓我們先看一下設定Hook的宣告吧。-----------------------------------------------------------------------------
HHOOK SetWindowsHookEx(
int idHook, // type of hook to install
HOOKPROC hkprc, // address of hook procedure
HINSTANCE hMod, // handle of application instance
DWORD dwThreadID // identity of thread to install hook for
);Declare Function SetWindowsHookEx Lib "user32" Alias SetWindowsHookExA" _
(ByVal idHook As Long, _
ByVal lpfn As Long, _
ByVal hmod As Long, _
ByVal dwThreadId As Long) As Long-----------------------------------------------------------------------------
Hook有很多種,如KeyBoard Hook, Mouse Hook, JournalRecord Hook等,所以第一個參數指明了要哪一種Hook,第二個參數便是Hook Procedure所在,也就是方才所說"自動執行某一段Function的功能"中的那一個Function,這個Function的名稱可以隨意給定,但有一定的參數傳遞規則,例如:
hnexthookproc = SetWindowsHookEx(WH_KEYBOARD, _
AddressOf MyKBHFunc, App.Hinstance, 0)
如此設定則每當按任一個鍵時,程式自動會去執行 MyKBHFunc。這個Hook Function是由我們所定義,但是它是由Window自動去呼叫,而不是由我們的程式呼叫,這類的Function就叫CallBack Function。其實,有個更直覺的例子,那就是事件(Event);比如說Form產生時會有一個Form_Load事件,在這個事件內我們可以寫很多程式,但這些程式不是給我們其他的Procedure呼叫的,而是Form在Load進來時,由Window去呼叫的,這便是CallBack Function。以上面的例子來說,這個CallBack Function定義如下:
-----------------------------------------------------------------------------
Public Function MyKBHFunc(ByVal iCode As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
MyKBHFunc = 0
If iCode < 0 Then
MyKBHFunc = CallNextHookEx(hnexthookproc, iCode, wParam, lParam)
Exit Function
End If
'偵測 有沒有按到PrintScreen鍵
If wParam = vbKeySnapshot Then
MyKBHFunc = 1
Debug.Print "haha"
End If
End Function
----------------------------------------------------------------------------- 這個KeyBoard Hook Function的目的主要是想攔截有沒有按到Print Screen這個鍵,這個鍵不會在Form的KeyDown, KeyPress, KeyUp Event中作用,所以只好透過KeyBoard Hook去攔截。而CallBack Function放的位置有規定,一個是要與呼叫SetWindowsHookEx() 的地方在同樣的一個Project,另外,它只能存在於.BAS檔,不能放在其他地方。KeyBoard Hook的程式於範五。範例五*****************************************************************************
'以下程式於Hook.bas
Declare Function SetWindowsHookEx Lib "user32" Alias _
"SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _
ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Declare Function UnhookWindowsHookEx Lib "user32" _
(ByVal hHook As Long) As Long
Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, _
ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As LongPublic hnexthookproc As Long
Public Const HC_ACTION = 0
Public Const WH_KEYBOARD = 2Public Sub UnHookKBD()
If hnexthookproc <> 0 Then
UnhookWindowsHookEx hnexthookproc
hnexthookproc = 0
End If
End Sub
Public Function EnableKBDHook()
If hnexthookproc <> 0 Then
Exit Function
End If
hnexthookproc = SetWindowsHookEx(WH_KEYBOARD, AddressOf _
MyKBHFunc, App.Hinstance, 0)
If hnexthookproc <> 0 Then
EnableKBDHook = hnexthookproc
End If
End Function
Public Function MyKBHFunc(ByVal iCode As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
'這三個參數是固定的,不能動,而MyKBHFunc這個名稱只要和
'SetWindowsHookex()中 AddressOf後的名稱一樣便可,不一定叫什麼
MyKBHFunc = 0
If iCode < 0 Then
MyKBHFunc = CallNextHookEx(hnexthookproc, iCode, wParam, lParam)
Exit Function
End If
If wParam = vbKeySnapshot Then '偵測 有沒有按到PrintScreen鍵
MyKBHFunc = 1
Debug.Print "haha"
End If
End Function
'以下程式於Form
Private Sub Form_Load()
Call EnableKBDHook
End SubPrivate Sub Form_Unload(Cancel As Integer)
Call UnHookKBD
End Sub
*****************************************************************************
Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" ( _
lpvDest As Any, lpvSource As Any, ByVal cbCopy as Long)
----------------------------------------------------------------------------- 這個函式可以將 lpvDest的momory copy 到lpvSource上去,cbCopy則代表要copy多少個byte。有了這個函式,我們可以知道一個Double值存在Memory中的各個byte到底是多少。-----------------------------------------------------------------------------
Dim dbl as Double
Dim bte(0 to 7) as Byte
Dbl = 168.256
CopyMemory dbl, byt(0), 8
----------------------------------------------------------------------------- 如此檢視bte陣列便可以知道這Double值的各個byte是多少。再以另一個JournalRecord Hook為例來說明:範例六*****************************************************************************
' 以下在Hook.bas
Const WM_MOUSELAST = &H209
Const WM_MOUSEFIRST = &H200
Public Const WM_KEYLAST = &H108
Public Const WM_KEYFIRST = &H100
Public Const WH_JOURNALRECORD = 0
Type EVENTMSG
message As Long
paramL As Long
paramH As Long
time As Long
hwnd As Long
End Type
Declare Function SetWindowsHookEx Lib "user32" Alias _
"SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _
ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Declare Function UnhookWindowsHookEx Lib "user32" _
(ByVal hHook As Long) As Long
Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, _
ByVal nCode As Long, ByVal wParam As Long, lParam As Any) As Long
Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" _
(lpvDest As Any, ByVal lpvSource As Long, ByVal cbCopy As Long)
Public hNxtHook As Long ' handle of Hook Procedure
Public msg As EVENTMSGSub EnableHook()
hNxtHook = SetWindowsHookEx(0, AddressOf HookProc, App.hInstance, 0)
End Sub
Sub FreeHook()
Dim ret As Long
ret = UnhookWindowsHookEx(hNxtHook)
End Sub
Function HookProc(ByVal code As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long
CopyMemory msg, lParam, Lenb(msg)
If (msg.message >= WM_KEYFIRST _
And msg.message <= WM_KEYLAST) Then
Debug.Print msg.message, msg.paramH
End If
HookProc = CallNextHookEx(hNxtHook, code, wParam, lParam)
End Function
'以下程式於Form1
Private Sub Form_Load()
Call EnableHook
End SubPrivate Sub Form_Unload(Cancel As Integer)
Call FreeHook
End Sub
***************************************************************************** 詳細的流程不多做說明,我們只把重點放在HookProc這個Hook Procedure,如果我們查JournalRecord Hook的Hook Procedure可得定義如下:-----------------------------------------------------------------------------
LRESULT CALLBACK JournalRecordProc(
int code, // hook code
WPARAM wParam, // undefined
LPARAM lParam // 為一個EVENTMSG Structure的address值
);這個JournalRecordProc 對應到我們的HookProc便是Function HookProc(ByVal code As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long-----------------------------------------------------------------------------
有沒有注意到第三個參數它是一個 ByVal的Long,指的是存放某一個EVENTMSG的位址,而先前我們提過,自定型態的參數傳遞要使用ByRef的方式才能解決,天啊!它用ByVal的方式來做,如果是C語言,那不成問題,只要如下:-----------------------------------------------------------------------------
EVENTMSG *p;
P = (EVENTMSG *) lParam;
-----------------------------------------------------------------------------
如此便可以用 *p->message 之方式來取得內容,但VB呢?這裡便要用些小技巧了,試想,如果我們能依lParam所指的位址,一個Byte一個Byte的Copy到一個EVENTMSG的變數上面,不就可以了嗎?所以了, CopyMomory這個函式派上用場了,但是 CopyMomory的原始宣告如下,前面兩個參數都是ByRef的方式,但目前對我們有的是lParam的內容(假設是lParam = 25600, Address of lParam = 100100),如果我們使用底下的宣告,而去呼叫
-- 宣告一 ----------------------------------------------------------------------
Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" ( _
lpvDest As Any, lpvSource As Any, ByVal cbCopy as Long) CopyMomory msg , lParam, Lenb(msg)
-----------------------------------------------------------------------------
那麼WinAPI RtlMoveMemory會得到第二個參數值=100100,而使指標指到100100的位址,那麼就得不到想要的資料了 (因資料在25600的位址上)。所以我們改變原始宣告,將之變成宣告二的樣子,如此VB 第二個參數的作法會傳出25600(因為ByVal嘛)給RtlMoveMemory,那不就成功了嗎?----宣告二 ---------------------------------------------------------------------
Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" ( _
lpvDest As Any, ByVal lpvSource As Long, ByVal cbCopy as Long)CopyMomory msg , lParam, Lenb(msg)
-----------------------------------------------------------------------------
或許這RtlMoveMemory您在許多地方都會用上,前兩個參數時而要ByRef, 時而需ByVal,那是否就要定義四個宣告來因應不同之需,其實也不用,上面的例子中,只要宣告成宣告一的樣子,但是呼叫時改成: CopyMemory msg, ByVal lParam, Lenb(msg)在第二個參數前加上ByVal這樣這可以了啦。 這裡還有另外一個做法,那就是從Hook Procedure的宣告著手,別忘了,Hook Procedure是Window所呼叫的,所以它傳給我們定義的HookProc()時,第三個參數以先前的舉例來說便是傳入25600,那麼,我們將HookProc()改定義成:-----------------------------------------------------------------------------
Function HookProc(ByVal code As Long, ByVal wParam As Long, _
lParam As Long) As Long
-----------------------------------------------------------------------------
第三個參數變成 ByRef的方式傳入,所以了,用msg = lParam來取代CopyMemory的作法, 嘛可以通啦!即如下:-----------------------------------------------------------------------------
Function HookProc(ByVal code As Long, ByVal wParam As Long, _
lParam As Long) As Long 'lParam改成ByRef
msg = lParam
'