一般会使用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 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, _
&nb
sp; 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
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