VB5.0与Windows API 间的呼叫技巧

jhjxxs

jhjxxs

2016-02-19 16:52

有一种朋友不在生活里,却在生命力;有一种陪伴不在身边,却在心间。图老师即在大家的生活中又在身边。这么贴心的服务你感受到了吗?话不多说下面就和大家分享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

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  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

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  然而要取得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的宣告如下:

  

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所在目录*****************************************************************************

  在范例四中,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*****************************************************************************

展开更多 50%)
分享

猜你喜欢

VB5.0与Windows API 间的呼叫技巧

编程语言 网络编程
VB5.0与Windows API 间的呼叫技巧

用VB5.0创建Windows快捷方式

编程语言 网络编程
用VB5.0创建Windows快捷方式

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

VB5.0中基于桌面的屏幕技巧

编程语言 网络编程
VB5.0中基于桌面的屏幕技巧

VB5.0中实现鼠标拖放

编程语言 网络编程
VB5.0中实现鼠标拖放

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

VB5.0应用程序的启动与退出设计

编程语言 网络编程
VB5.0应用程序的启动与退出设计

VB5.0中实现系统登录

编程语言 网络编程
VB5.0中实现系统登录

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

上网前宽带连接打不开怎么办

上网前宽带连接打不开怎么办

取得File 8.3之文件名称格式

取得File 8.3之文件名称格式
下拉加载更多内容 ↓