一般会使用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
Eg.
-----------------------------------------------------------------------------
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 IntegerInsertMode = GetKeyState(vbKeyInsert) And vbShiftMaskIf 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的Product ID该如何做呢?这里有几个观念要先清楚:首先:ProductId在何处呢?在HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVerson下的ProductId。我们要取得的便是KEY 为 HKEY_LOCAL_MACHINE
SUBKEY 为 SOFTWAREMicrosoftWindowsCurrentVerson
ValueName 为 ProductId 的value
然而要取得ProductId的value可没那麽直接,要先取得SubKey的KeyHandle而KeyHandle的取得便是利用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 !
而BSTR则在字串的前面还有一个LONG值存字串长度,格式如下:
Address 0.. 3 4 5 6 7 8 9 10 11 12 13
------ -- -- -- -- -- -- -- -- -- --
内容 9 H a H a ! O K !
-----------------------------------------------------------------------------
所以了字串以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 劤 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的宣告如下:
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)
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 LongDim lpString As StringDim tmpstr As StringDim NullPos As LongForm1.Caption = "这是一个test"lpString = String(255, 0) '设定初值CharCnt = GetWindowText(Me.hwnd, lpString, 256) 'CharCnt = 12tmpstr = Left(lpString, CharCnt) '如此做会有一些问题Debug.Print Len(tmpstr) '得12Label1.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 = 12NullPos = 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 LongDeclare 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 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 LongConst REG_EXPAND_SZ = 2Const HKEY_CLASSES_ROOT = &H80000000Const READ_CONTROL = &H20000Const STANDARD_RIGHTS_READ = (READ_CONTROL)Const KEY_QUERY_VALUE = &H1Const KEY_ENUMERATE_SUB_KEYS = &H8Const KEY_NOTIFY = &H10Const SYNCHRONIZE = &H100000Const 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 StringDim leng1 As Long, resul As Long, hkey As LongDim tp As Long, i As Longkey5 = " SOFTWAREMicrosoftWindowsCurrentVerson "resul = RegOpenKeyEx(HKEY_CLASSES_ROOT, key5, 0, KEY_READ, hkey) 'hkey便是subkey (key5)的KeyHandle,先取得它才能存取Subkey内的ValueNameValueName= "ProDuctId "tp = REG_SZstrBuff = String(255, 0)leng1 = Len(strBuff) + 1resul = 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 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 LongDim Buff() as ByteDim StrA as StringBuff = space(256)n=GetWindowsDirectory(Buff(0), 256)Buff = Leftb(Buff, n)StrA = StrConv(Buff, vbUniCode) 'StrA便是Windows所在目录*****************************************************************************(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)
在范例四中,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 IfEnd Function-----------------------------------------------------------------------------
这个KeyBoard Hook Function的目的主要是想拦截有没有按到Print Screen这个键,这个键不会在Form的KeyDown, KeyPress, KeyUp Event中作用,所以只好透过KeyBoard Hook去拦截。而CallBack Function放的位置有规定,一个是要与呼叫SetWindowsHookEx() 的地方在同样的一个Project,另外,它只能存在於.BAS档,不能放在其他地方。KeyBoard Hook的程式於范五。
范例五
*****************************************************************************'以下程式於Hook.basDeclare Function SetWindowsHookEx Lib "user32" Alias _"SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _ByVal hmod As Long, ByVal dwThreadId As Long) As LongDeclare Function UnhookWindowsHookEx Lib "user32" _ (ByVal hHook As Long) As LongDeclare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, _ ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As LongPublic hnexthookproc As LongPublic Const HC_ACTION = 0Public Const WH_KEYBOARD = 2Public Sub UnHookKBD()If hnexthookproc 0 Then UnhookWindowsHookEx hnexthookproc hnexthookproc = 0End IfEnd SubPublic Function EnableKBDHook()If hnexthookproc 0 Then Exit FunctionEnd Ifhnexthookproc = SetWindowsHookEx(WH_KEYBOARD, AddressOf _ MyKBHFunc, App.Hinstance, 0)If hnexthookproc 0 Then EnableKBDHook = hnexthookprocEnd IfEnd FunctionPublic 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 IfIf wParam = vbKeySnapshot Then '侦测 有没有按到PrintScreen键 MyKBHFunc = 1 Debug.Print "haha"End IfEnd Function'以下程式於FormPrivate Sub Form_Load()Call EnableKBDHookEnd SubPrivate Sub Form_Unload(Cancel As Integer)Call UnHookKBDEnd Sub*****************************************************************************