本页主题: [转贴]vb制作游戏外挂教程菜鸟级 打印 | 加为IE收藏 | 复制链接 | 收藏主题 | 上一主题 | 下一主题

superttl
级别: 论坛版主


精华: 1
发帖: 578
威望: 138 点
金钱: 586 胜利币
贡献值: 0 点
在线时间:499(小时)
注册时间:2006-04-20
最后登录:2008-11-08

 [转贴]vb制作游戏外挂教程菜鸟级

0
管理提醒:
本帖被 lms7888 执行加亮操作(2008-03-05)
有个问题必须首先考虑:使用VB编写的修改器需要VB的执行库才能执行。如果考虑到有些使用者(实际上可能是大部分使用者)没有执行库,那么在最后制作的ZIP压缩档中就必须包含这些庞大的档。在下面的教程里我将制作一个修改器,如果为它再制作一个安装程序,那么整个修改器的体积将超过1MB。其中包括一个很好的安装和反安装程序,但大部分还是VB40032.DLL这个档。
除了以上这点,使用VB制作修改器是非常简单的。一旦制作了多次后,你会发现能很快地制作出一个修改器。而且使用VB制作的修改器能够毫无困难地解决游戏执行时的动态内存分配问题,因此即使是最新的游戏,也可以使用VB制作修改器。在本教程中将不涉及动态内存分配,因为虽然简单,但仍然属于一个高级的选项。
一些背景知识
不象C语音,VB不会自动包括普通的API函数的声明,因此我们必须把他们加入我们的项目文件。在几乎所有的修改器中会使用到6个主要的函数,讨论如下:
1. FindWindow(ClassName, WindowTitle) - FindWindow 返回符合指定的类名( ClassName )和窗口名( WindowTitle )的窗口句柄。对我们来说,可以让 ClassName 为空( Null ),只给出游戏的 WindowTitle。
函数应该这样声明:
Declare Function FindWindow Lib user32 Alias FindWindowA (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
2. GetWindowThreadProcessId(WindowHandle, ProcessId) - 在这里我们把 FindWindow
函数中得到的句柄作为参数,来获得进程标识符(ProcessId )。
声明如下:
Declare Function GetWindowThreadProcessId Lib user32 (ByVal hwnd As Long, lpdwProcessId As Long) As Long
3. OpenProcess(DesiredAccess, Inherit, ProcessId) -
这个函数将返回一个我们目标进程的句柄,可以用来对目标进行读写操作。 DesiredAccess参数的值决定了句柄对进程的存取权利,对我们来说,要使用 PROCESS_ALL_ACCESS (完全存取权限)。Inherit 应该总是False。 ProcessId 是从 GetWindowThreadProcessId 函数中取得的。
Declare Function OpenProcess Lib kernel32 (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
4. CloseHandle(ProcessHandle) - 每一个打开的句柄必须呼叫这个函数来关闭。
Declare Function CloseHandle Lib kernel32 (ByVal hObject As Long) As Long
5. WriteProcessMemory(ProcessHandle, Address, value, Sizeofvalue,
BytesWritten) - 把指定的值 value 写入由 Address 指定的目标地址。
Declare Function WriteProcessMemory Lib kernel32 (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
6. ReadProcessMemory(ProcessHandle, Address, value, Sizeofvalue,
BytesWritten) - 把 Address 指定的目标地址的值存入 value 位置的变量中。
Declare Function WriteProcessMemory Lib kernel32 (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
这些函数一环扣一环,缺一不可。更详细的内容可以参考VB的帮助档。

一个简单的修改器范例
如何使上面介绍的这些函数一起工作,制作出我们需要的修改器呢?下面是一个为Windows的计算器程序制作修改器的例子。这个修改器将读出计算器窗口中显示的数值,并在点击一个按钮后在计算器窗口中显示我们的名字。
首先我们需要找到计算器显示窗口中显示值的地址。本教程不是关于如何进行内存搜索,因而我将只作简单的说明:
- 在计算器窗口中输入123456
- 使用你喜欢的任何一种内存地址搜索程序寻找字符串123456
- 使用另一个值重复上面的过程直到只返回1个地址
那是制作我们的修改器需要的唯一一个地址。在我的计算器程序里这个地址是40B181 hex, 4239745 dec。用你找到的地址替代在下面的代码里使用的这个地址。
现在让我们开始设计修改器的接口:
- 在VB中新建一个项目,加入一个文本框( Textbox )、一个按钮和一个定时器( timer
)。文本框用来显示从计算器窗口取得的字符串,按钮用来把我们的名字传到计算器窗口
- 把窗体( form )的标题( Caption )内容设为 Calculator Trainer
- 把文本框改名为 txtDisplay 并清除 Text 内容
- 把定时器改名为 ReadTimer 并把间隔( interval )设为500
- 把按钮的标题改为 Display Name,按钮的名字改为 btnPasteName
在这个修改器中我们将使用所有6个函数,ReadProcessMemory、WriteProcessMemory、OpenProcess、 GetWindowThreadProcessId、FindWindow 和 CloseHandle。在项目中插入一个新的模块,增加下列代码。(下面的一些行自动换行了,在你的模块中每一句必须在一行里,或使用延长符_)
Declare Function FindWindow Lib user32 Alias FindWindowA (ByVal
lpClassName As String, ByVal lpWindowName As String) As Long
Declare Function GetWindowThreadProcessId Lib user32 (ByVal hwnd As
Long, lpdwProcessId As Long) As Long
Declare Function OpenProcess Lib kernel32 (ByVal dwDesiredAccess As
Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Declare Function WriteProcessMemory Lib kernel32 (ByVal hProcess As
Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As
Long, lpNumberOfBytesWritten As Long) As Long
Declare Function ReadProcessMemory Lib kernel32 (ByVal hProcess As Long,
ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long,
lpNumberOfBytesWritten As Long) As Long
Declare Function CloseHandle Lib kernel32 (ByVal hObject As Long) As Long
下面我们要开始写在定时器窗口中显示我们名字的代码了。首先我们使用 FindWindow函数取得目标窗口的句柄。把这个返回值保存在一个变量中,并检查它的值是否出错来确保定时器程序正在执行。(FindWindow函数出错时返回0)
Dim hwnd As Long
hwnd = FindWindow(vbNullString, Calculator)
If (hwnd = 0) Then
MsgBox Window not found!
Exit Sub
End If
注意在这里我们传递了一个 Null 值给 FindWindow 函数,而不是 ClassName。因此任何名为 Calculator的窗口都符合条件。如果知道计算器程序窗口的 ClassName,你可以传给它,但这不是必须的。
现在使用得到的窗口句柄来取得进程标识符( ProcessId )。注意 pid 是作为参数传递给函数的,而不是被赋以函数返回值。
Dim pid As Long
GetWindowThreadProcessId hwnd, pid
再利用变量pid得到计算器程序的进程句柄。再次检查函数的返回值,如果是非法数据则退出程序。
Dim pHandle As Long
pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
If (pHandle = 0) Then
MsgBox Couldn’t get a process handle!
Exit Sub
End If
在我们的修改器中 WriteProcessMemory 函数是最重要的部分,而且非常容易出错。不妨让我们再仔细讨论一下它的参数。
WriteProcessMemory (ByVal hProcess As Long, ByVal lpBaseAddress As Any,
ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As)
hProcess 是目标进程的句柄,从上面的 OpenProcess 函数中取得的。
lpBaseAddress
是在计算器程序的虚拟内存中将要被修改的地址,也就是使用内存搜索程序找到的那个地址。(在我的程序里是&H40B181)lpBuffer 是将要写如上述地址的数据,可以是一个数值、数组、字符串或其它任何数据类型。
nSize 是希望写入 lpBaseAddress 的字节数。这个位置应该与你的数据类型相符。如果写入的是一个长整数( long),这里应该是4。如果写入的是一个字符串,那么这里应该是字符串的长度。
lpNumberOfBytesWritten 是函数执行返回后,写入目标地址的实际字节数。它能被用来确认函数实际的执行情况。
把我们的数据放到函数中,得到 WriteProcessMemory pHandle, &H40B181, Beans, 5, 0&。我把0传递到lpNumberOfBytesWritten 位置是因为不需要检查两次实际写入的字节数。
最后通过传递进程句柄给 CloseHandle() 函数来关闭由 OpenProcess 打开的句柄。
CloseHandle hProcess
现在将所有的代码输入我们的编辑器中。双击按钮,显示它的代码编辑窗口。代码应该加到名为 btnPasteName 的 Click事件中。(不必输入注释)
Private Sub btnPasteName_Click()
声明一些需要的变量
Dim hwnd As Long 储存 FindWindow 函数返回的句柄
Dim pid As Long 储存进程标识符( Process Id )
Dim pHandle As Long 储存进程句柄
首先取得目标窗口的句柄
hwnd = FindWindow(vbNullString, Calculator)
If (hwnd = 0) Then
MsgBox Window not found!
Exit Sub
End If
取得进程标识符
GetWindowThreadProcessId hwnd, pid
使用进程标识符取得进程句柄
pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
If (pHandle = 0) Then
MsgBox Couldn’t get a process handle!
Exit Sub
End If
在内存地址中写入名字
WriteProcessMemory pHandle, &H40B181, Beans, 5, 0&
关闭进程句柄
CloseHandle hProcess
End Sub
完毕。现在单击按钮将使计算器窗口文本变为我们键如的名字。(可能需要最小化计算器程序,再还原,以便程序更新显示)
下面将给我们的修改器增加一个新功能。我们将检测计算器程序的窗口显示数据,并在修改器中显示。双击定时器,显示它的代码编辑窗口,然后输入以下代码:
Private Sub ReadTimer_Timer()
声明变数
Dim hwnd As Long 储存 FindWindow 函数返回的句柄
Dim pid As Long 储存进程标识符
Dim pHandle As Long 储存进程句柄
Dim str As String * 20 存储显示文本
取得目标窗口的句柄
hwnd = FindWindow(vbNullString, Calculator)
If (hwnd = 0) Then Exit Sub
取得进程标识符
GetWindowThreadProcessId hwnd, pid
取得进程句柄
pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
If (pHandle = 0) Then Exit Sub
读取内存数据
ReadProcessMemory pHandle, &H40B181, str, 20, 0&
在文本框显示
txtDisplay = str
关闭进程句柄
CloseHandle hProcess
End Sub
在这里出现的新东西是 ReadProcessMemory 函数。从 &H40B181 地址中读出的数据被存入变量 str 中,然后显示在名为txtDisplay 的文本框中。
本教程中所讲的是非常简单的东西,主要是想起抛砖引玉的目的。最重要的是不断学习,不断实践,了解其它的API并在修改器中使用。练习越多,就会觉得越容易。
好的话帮忙顶一下,谢谢!
刚才查别的资料的时候偶尔看到的,感觉比较适合菜鸟。
还算不上做外挂,顶多算个内存修改器
WriteProcessMemory还是声明为Byval lpBaseAddress As Long, ByVal lpBuffer As Long来的好
然后用VarPtr传变量的指针地址
写nSize的时候,Long之所以要传4,是因为Long类型的变量在内存中占用4个字节,你也可以统统用Len(不过我还是建议你用 API lstrlen,因为nSize是字节数,而非字符数),因为Len也可以用于Long(以及Integer,甚至于用户类型),并且返回值一定是4。
lpNumberOfBytesWritten 将会被设置为真正成功写入内存的字节数(所以你最好传一个变量,而不是ByVal 0&)。
ReadProcessMemory我也建议你如WriteProcessMemory那样声明
因为这样可以减少许多不必要的错误
同样的,lpNumberOfBytesWritten 也将会被设置为真正读入的字节
所以你可以先将一个String变量设置为4000个空格(Space$(4000))(甚至更多),然后通过CopyMemory VatPtr(str_真正读到的内容),VarPtr(str_Buffer),lpNumberOfBytesWritten(这个 CopyMemory是声明为ByVal src As Long, ByVal tgt As Long, ByVal nSize As Long的)来获取真正的字节数。
不过,在这儿你不需要这么操作,因为你是可以设置读取的字节数的(nSize参数),而当你使用ReadFile之类的函数时,你会发现这个参数很有用处。
同样的,你也可以传ByVal 0&,但是我仍然不建议你这么做
VarPtr是VB中获取变量名在内存中的地址用的函数(不是变量内容,变量内容你应当使用的是StrPtr而不是VarPtr)
使用VarPtr并且声明为 ByVal *** As Long同样也适用于CopyMemory。

    当用户按下键盘上的一个键时,键盘内的芯片会检测到这个动作,并把这个信号传送到计算机。如何区别是哪一个键被按下了呢?键盘上的所有按键都有一个编码,称作键盘扫描码。当你按下一个键时,这个键的扫描码就被传给系统。扫描码是跟具体的硬件相关的,同一个键,在不同键盘上的扫描码有可能不同。键盘控制器就是将这个扫描码传给计算机,然后交给键盘驱动程序。键盘驱动程序会完成相关的工作,并把这个扫描码转换为键盘虚拟码。什么是虚拟码呢?因为扫描码与硬件相关,不具有通用性,为了统一键盘上所有键的编码,于是就提出了虚拟码概念。无论什么键盘,同一个按键的虚拟码总是相同的,这样程序就可以识别了。简单点说,虚拟码就是我们经常可以看到的像VK_A,VK_B这样的常数,比如键A的虚拟码是65,写成16进制就是&H41,注意,人们经常用16进制来表示虚拟码。当键盘驱动程序把扫描码转换为虚拟码后,会把这个键盘操作的扫描码和虚拟码还有其它信息一起传递给操作系统。然后操作系统则会把这些信息封装在一个消息中,并把这个键盘消息插入到消息列队。最后,要是不出意外的话,这个键盘消息最终会被送到当前的活动窗口那里,活动窗口所在的应用程序接收到这个消息后,就知道键盘上哪个键被按下,也就可以决定该作出什么响应给用户了。这个过程可以简单的如下表示:
用户按下按键-----键盘驱动程序将此事件传递给操作系统-----操作系统将键盘事件插入消息队列-----键盘消息被发送到当前活动窗口
明白了这个过程,我们就可以编程实现在其中的某个环节来模拟键盘操作了。在VB中,有多种方法可以实现键盘模拟,我们就介绍几种比较典型的。

1.局部级模拟

    从上面的流程可以看出,键盘事件是最终被送到活动窗口,然后才引起目标程序响应的。那么最直接的模拟方法就是:直接伪造一个键盘消息发给目标程序。哈哈,这实在是很简单,windows提供了几个这样的API函数可以实现直接向目标程序发送消息的功能,常用的有SendMessage和 PostMessage,它们的区别是PostMessage函数直接把消息仍给目标程序就不管了,而SendMessage把消息发出去后,还要等待目标程序返回些什么东西才好。这里要注意的是,模拟键盘消息一定要用PostMessage函数才好,用SendMessage是不正确的(因为模拟键盘消息是不需要返回值的,不然目标程序会没反应),切记切记!PostMessage函数的VB声明如下:
Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
参数hwnd 是你要发送消息的目标程序上某个控件的句柄,参数wMsg 是消息的类型,表示你要发送什么样的消息,最后wParam 和lParam 这两个参数是随消息附加的数据,具体内容要由消息决定。
再来看看wMsg 这个参数,要模拟按键就靠这个了。键盘消息常用的有如下几个:
WM_KEYDOWN      表示一个普通键被按下
WM_KEYUP        表示一个普通键被释放
WM_SYSKEYDOWN  表示一个系统键被按下,比如Alt键
WM_SYSKEYUP    表示一个系统键被释放,比如Alt键
如果你确定要发送以上几个键盘消息,那么再来看看如何确定键盘消息中的wParam 和lParam 这两个参数。在一个键盘消息中,wParam 参数的含义较简单,它表示你要发送的键盘事件的按键虚拟码,比如你要对目标程序模拟按下A键,那么wParam 参数的值就设为VK_A ,至于lParam 这个参数就比较复杂了,因为它包含了多个信息,一般可以把它设为0,但是如果你想要你的模拟更真实一些,那么建议你还是设置一下这个参数。那么我们就详细了解一下lParam 吧。lParam 是一个long类型的参数,它在内存中占4个字节,写成二进制就是00000000 00000000 00000000 00000000  一共是32位,我们从右向左数,假设最右边那位为第0位(注意是从0而不是从1开始计数),最左边的就是第31位,那么该参数的的0-15位表示键的发送次数等扩展信息,16-23位为按键的扫描码,24-31位表示是按下键还是释放键。大家一般习惯写成16进制的,那么就应该是&H00 00 00 00 ,第0-15位一般为&H0001,如果是按下键,那么24-31位为&H00,释放键则为&HC0,那么16-23位的扫描码怎么会得呢?这需要用到一个API函数MapVirtualKey,这个函数可以将虚拟码转换为扫描码,或将扫描码转换为虚拟码,还可以把虚拟码转换为对应字符的ASCII码。它的VB声明如下:
Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long
参数wCode 表示待转换的码,参数wMapType 表示从什么转换为什么,如果是虚拟码转扫描码,则wMapType 设置为0,如果是虚拟扫描码转虚拟码,则wMapType 设置为1,如果是虚拟码转ASCII码,则wMapType 设置为2.相信有了这些,我们就可以构造键盘事件的lParam参数了。下面给出一个构造lParam参数的函数:
Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long

Function MakeKeyLparam(ByVal VirtualKey As Long, ByVal flag As Long) As Long
'参数VirtualKey表示按键虚拟码,flag表示是按下键还是释放键,用WM_KEYDOWN和WM_KEYUP这两个常数表示
    Dim s As String
    Dim Firstbyte As String    'lparam参数的24-31位
    If flag = WM_KEYDOWN  Then '如果是按下键
        Firstbyte = "00"
    Else
        Firstbyte = "C0"        '如果是释放键
    End If
    Dim Scancode As Long
    '获得键的扫描码
    Scancode = MapVirtualKey(VirtualKey, 0)
    Dim Secondbyte As String    'lparam参数的16-23位,即虚拟键扫描码
    Secondbyte = Right("00" & Hex(Scancode), 2)
    s = Firstbyte & Secondbyte & "0001"  '0001为lparam参数的0-15位,即发送次数和其它扩展信息
    MakeKeyLparam = Val("&H" & s)
End Function

这个函数像这样调用,比如按下A键,那么lParam=MakeKeyLparam(VK_A,WM_KEYDOWN) ,很简单吧。值得注意的是,即使你发送消息时设置了lParam参数的值,但是系统在传递消息时仍然可能会根据当时的情况重新设置该参数,那么目标程序收到的消息中lParam的值可能会和你发送时的有所不同。所以,如果你很懒的话,还是直接把它设为0吧,对大多数程序不会有影响的,呵呵。
    好了,做完以上的事情,现在我们可以向目标程序发送键盘消息了。首先取得目标程序接受这个消息的控件的句柄,比如目标句柄是12345,那么我们来对目标模拟按下并释放A键,像这样:(为了简单起见,lParam这个参数就不构造了,直接传0)
PostMessage 12345,WM_KEYDOWN,VK_A,0&    '按下A键
PostMessage 12345,WM_UP,VK_A,0&        '释放A键
好了,一次按键就完成了。现在你可以迫不及待的打开记事本做实验,先用FindWindowEx这类API函数找到记事本程序的句柄,再向它发送键盘消息,期望记事本里能诡异的自动出现字符。可是你马上就是失望了,咦,怎么一点反应也没有?你欺骗感情啊 ~~~~~~~~~~55555555555555  不是的哦,接着往下看啊。
一般目标程序都会含有多个控件,并不是每个控件都会对键盘消息作出反应,只有把键盘消息发送给接受它的控件才会得到期望的反应。那记事本来说,它的编辑框其实是个edit类,只有这个控件才对键盘事件有反应,如果只是把消息发给记事本的窗体,那是没有用的。现在你找出记事本那个编辑框的句柄,比如是 54321,那么写如下代码:
PostMessage 54321,WM_KEYDOWN,VK_F1,0&    '按下F1键
PostMessage 54321,WM_UP,VK_F1,0&        '释放F1键
怎么样,是不是打开了记事本的“帮助”信息?这说明目标程序已经收到了你发的消息,还不错吧~~~~~~~~
可以马上新问题就来了,你想模拟向记事本按下A这个键,好在记事本里自动输入字符,可是,没有任何反应!这是怎么一回事呢?
原来,如果要向目标程序发送字符,光靠WM_KEYDOWN和WM_UP这两个事件还不行,还需要一个事件:WM_CHAR,这个消息表示一个字符,程序需靠它看来接受输入的字符。一般只有A,B,C等这样的按键才有WM_CHAR消息,别的键(比如方向键和功能键)是没有这个消息的,WM_CHAR消息一般发生在WM_KEYDOWN消息之后。WM_CHAR消息的lParam参数的含义与其它键盘消息一样,而它的wParam则表示相应字符的 ASCII编码(可以输入中文的哦^_^),现在你可以写出一个完整的向记事本里自动写入字符的程序了,下面是一个例子,并附有这些消息常数的具体值:
Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long

Public Const WM_KEYDOWN = &H100
Public Const WM_KEYUP = &H101
Public Const WM_CHAR = &H102
Public Const VK_A = &H41

Function MakeKeyLparam(ByVal VirtualKey As Long, ByVal flag As Long) As Long
    Dim s As String
    Dim Firstbyte As String    'lparam参数的24-31位
    If flag = WM_KEYDOWN  Then '如果是按下键
        Firstbyte = "00"
    Else
        Firstbyte = "C0"        '如果是释放键
    End If
    Dim Scancode As Long
    '获得键的扫描码
    Scancode = MapVirtualKey(VirtualKey, 0)
    Dim Secondbyte As String    'lparam参数的16-23位,即虚拟键扫描码
    Secondbyte = Right("00" & Hex(Scancode), 2)
    s = Firstbyte & Secondbyte & "0001"  '0001为lparam参数的0-15位,即发送次数和其它扩展信息
    MakeKeyLparam = Val("&H" & s)
End Function

Private Sub Form_Load()
    dim hwnd as long
    hwnd = XXXXXX  'XXXXX表示记事本编辑框的句柄
    PostMessage hwnd,WM_KEYDOWN,VK_A,MakeKeyLparam(VK_A,WM_KEYDOWN)  '按下A键
    PostMessage hwnd,WM_CHAR,ASC("A"),MakeKeyLparam(VK_A,WM_KEYDOWN)  '输入字符A
    PostMessage hwnd,WM_UP,VK_A,MakeKeyLparam(VK_A,WM_UP)        '释放A键
End Sub

这就是通过局部键盘消息来模拟按键。这个方法有一个极大的好处,就是:它可以实现后台按键,也就是说他对你的前台操作不会有什么影响。比如,你可以用这个方法做个程序在游戏中模拟按键来不断地执行某些重复的操作,而你则一边喝茶一边与QQ上的MM们聊得火热,它丝毫不会影响你的前台操作。无论目标程序是否获得焦点都没有影响,这就是后台模拟按键的原理啦~~~~


2.全局级模拟

    你会发现,用上面的方法模拟按键并不是对所有程序都有效的,有的程序啊,你向它发了一大堆消息,可是它却一点反应也没有。这是怎么回事呢?这就要看具体的情况了,有些程序(特别是一些游戏)出于某些原因,会禁止用户对它使用模拟按键程序,这个怎么实现呢?比如可以在程序中检查一下,如果发现自己不是活动窗口,就不接受键盘消息。或者仔细检查一下收到的键盘消息,你会发现真实的按键和模拟的按键消息总是有一些小差别,从这些小差别上,目标程序就能判断出:这是假的!是伪造的!!因此,如果用PostMessage发送局部消息模拟按键不成功的话,你可以试一试全局级的键盘消息,看看能不能骗过目标程序。
模拟全局键盘消息常见的可以有以下一些方法:
(1) 用API函数keybd_event,这个函数可以用来模拟一个键盘事件,它的VB声明为:
Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
参数bVk表示要模拟的按键的虚拟码,bScan表示该按键的扫描码(一般可以传0),dwFlags表示是按下键还是释放键(按下键为0,释放键为2),dwExtraInfo是扩展标志,一般没有用。比如要模拟按下A键,可以这样:
Const KEYEVENTF_KEYUP = &H2
keybd_event VK_A, 0, 0, 0    '按下A键
keybd_event VK_A, 0, KEYEVENTF_KEYUP, 0    '释放A键
注意有时候按键的速度不要太快,否则会出问题,可以用API函数Sleep来进行延时,声明如下:
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
参数dwMilliseconds表示延时的时间,以毫秒为单位。
那么如果要模拟按下功能键怎么做呢?比如要按下Ctrl+C实现拷贝这个功能,可以这样:
keybd_event VK_Ctrl, 0, 0, 0    '按下Ctrl键
keybd_event VK_C, 0, 0, 0      '按下C键
Sleep 500            '延时500毫秒
keybd_event VK_C, 0, KEYEVENTF_KEYUP, 0    '释放C键
keybd_event VK_Ctrl, 0, KEYEVENTF_KEYUP, 0    '释放Ctrl键
好了,现在你可以试试是不是可以骗过目标程序了,这个函数对大部分的窗口程序都有效,可是仍然有一部分游戏对它产生的键盘事件熟视无睹,这时候,你就要用上bScan这个参数了。一般的,bScan都传0,但是如果目标程序是一些DirectX游戏,那么你就需要正确使用这个参数传入扫描码,用了它可以产生正确的硬件事件消息,以被游戏识别。这样的话,就可以写成这样:
keybd_event VK_A, MapVirtualKey(VK_A, 0), 0, 0    '按下A键
keybd_event VK_A, MapVirtualKey(VK_A, 0), KEYEVENTF_KEYUP, 0    '释放A键
以上就是用keybd_event函数来模拟键盘事件。除了这个函数,SendInput函数也可以模拟全局键盘事件。SendInput可以直接把一条消息插入到消息队列中,算是比较底层的了。它的VB声明如下:
Declare Function SendInput Lib "user32.dll" (ByVal nInputs As Long, pInputs As GENERALINPUT, ByVal cbSize As Long) As Long
参数:
nlnprts:定义plnputs指向的结构的数目。
plnputs:指向INPUT结构数组的指针。每个结构代表插人到键盘或鼠标输入流中的一个事件。
cbSize:定义INPUT结构的大小。若cbSize不是INPUT结构的大小,则函数调用失败。
返回值:函数返回被成功地插人键盘或鼠标输入流中的事件的数目。若要获得更多的错误信息,可以调用GetlastError函数。
备注:Sendlnput函数将INPUT结构中的事件顺序地插入键盘或鼠标的输入流中。这些事件与用户插入的(用鼠标或键盘)或调用keybd_event,mouse_event,或另外的Sendlnput插人的键盘或鼠标的输入流不兼容。
嗯,这个函数用起来蛮复杂的,因为它的参数都是指针一类的东西。要用它来模拟键盘输入,先要构造一组数据结构,把你要模拟的键盘消息装进去,然后传给它。为了方便起见,把它做在一个过程里面,要用的时候直接调用好了,代码如下:
Declare Function SendInput Lib "user32.dll" (ByVal nInputs As Long, pInputs As GENERALINPUT, ByVal cbSize As Long) As Long
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal ByteLen As Long)
Type GENERALINPUT
    dwType As Long
    xi(0 To 23) As Byte
End Type

Type KEYBDINPUT
  wVk As Integer
  wScan As Integer
  dwFlags As Long
  time As Long
  dwExtraInfo As Long
End Type

Const INPUT_KEYBOARD = 1

Sub MySendKey(bkey As Long)
'参数bkey传入要模拟按键的虚拟码即可模拟按下指定键
Dim GInput(0 To 1) As GENERALINPUT
Dim KInput As KEYBDINPUT
KInput.wVk = bkey  '你要模拟的按键
KInput.dwFlags = 0 '按下键标志
GInput(0).dwType = INPUT_KEYBOARD
CopyMemory GInput(0).xi(0), KInput, Len(KInput) '这个函数用来把内存中KInput的数据复制到GInput
KInput.wVk = bkey 
KInput.dwFlags = KEYEVENTF_KEYUP  ' 释放按键
GInput(1).dwType = INPUT_KEYBOARD ' 表示该消息为键盘消息
CopyMemory GInput(1).xi(0), KInput, Len(KInput)
'以上工作把按下键和释放键共2条键盘消息加入到GInput数据结构中
SendInput 2, GInput(0), Len(GInput(0))    '把GInput中存放的消息插入到消息列队
End Sub

    除了以上这些,用全局钩子也可以模拟键盘消息。如果你对windows中消息钩子的用法已经有所了解,那么你可以通过设置一个全局HOOK来模拟键盘消息,比如,你可以用WH_JOURNALPLAYBACK这个钩子来模拟按键。WH_JOURNALPLAYBACK是一个系统级的全局钩子,它和 WH_JOURNALRECORD的功能是相对的,常用它们来记录并回放键盘鼠标操作。WH_JOURNALRECORD钩子用来将键盘鼠标的操作忠实地记录下来,记录下来的信息可以保存到文件中,而WH_JOURNALPLAYBACK则可以重现这些操作。当然亦可以单独使用 WH_JOURNALPLAYBACK来模拟键盘操作。你需要首先声明SetWindowsHookEx函数,它可以用来安装消息钩子:
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
先安装WH_JOURNALPLAYBACK这个钩子,然后你需要自己写一个钩子函数,在系统调用它时,把你要模拟的事件传递给钩子参数lParam所指向的EVENTMSG区域,就可以达到模拟按键的效果。不过用这个钩子模拟键盘事件有一个副作用,就是它会锁定真实的鼠标键盘,不过如果你就是想在模拟的时候不会受真实键盘操作的干扰,那么用用它倒是个不错的主意。
3.驱动级模拟

    如果上面的方法你都试过了,可是你发现目标程序却仍然顽固的不接受你模拟的消息,寒~~~~~~~~~还好,我还剩下最后一招,这就是驱动级模拟:直接读写键盘的硬件端口!
    有一些使用DirectX接口的游戏程序,它们在读取键盘操作时绕过了windows的消息机制,而使用DirectInput.这是因为有些游戏对实时性控制的要求比较高,比如赛车游戏,要求以最快速度响应键盘输入。而windows消息由于是队列形式的,消息在传递时会有不少延迟,有时1秒钟也就传递十几条消息,这个速度达不到游戏的要求。而DirectInput则绕过了windows消息,直接与键盘驱动程序打交道,效率当然提高了不少。因此也就造成,对这样的程序无论用PostMessage或者是keybd_event都不会有反应,因为这些函数都在较高层。对于这样的程序,只好用直接读写键盘端口的方法来模拟硬件事件了。要用这个方法来模拟键盘,需要先了解一下键盘编程的相关知识。
    在DOS时代,当用户按下或者放开一个键时,就会产生一个键盘中断(如果键盘中断是允许的),这样程序会跳转到BIOS中的键盘中断处理程序去执行。打开 windows的设备管理器,可以查看到键盘控制器由两个端口控制。其中&H60是数据端口,可以读出键盘数据,而&H64是控制端口,用来发出控制信号。也就是,从&H60号端口可以读此键盘的按键信息,当从这个端口读取一个字节,该字节的低7位就是按键的扫描码,而高1位则表示是按下键还是释放键。当按下键时,最高位为0,称为通码,当释放键时,最高位为1,称为断码。既然从这个端口读数据可以获得按键信息,那么向这个端口写入数据就可以模拟按键了!用过QbASIC4.5的朋友可能知道,QB中有个OUT命令可以向指定端口写入数据,而INP函数可以读取指定端口的数据。那我们先看看如果用QB该怎么写代码:
假如你想模拟按下一个键,这个键的扫描码为&H50,那就这样
OUT &H64,&HD2    '把数据&HD2发送到&H64端口。这是一个KBC指令,表示将要向键盘写入数据
OUT &H60,&H50    '把扫描码&H50发送到&H60端口,表示模拟按下扫描码为&H50的这个键
那么要释放这个键呢?像这样,发送该键的断码:
OUT &H64,&HD2    '把数据&HD2发送到&H64端口。这是一个KBC指令,表示将要向键盘写入数据
OUT &H60,(&H50 OR &H80)    '把扫描码&H50与数据&H80进行或运算,可以把它的高位置1,得到断码,表示释放这个键
    好了,现在的问题就是在VB中如何向端口写入数据了。因为在windows中,普通应用程序是无权操作端口的,于是我们就需要一个驱动程序来帮助我们实现。在这里我们可以使用一个组件WINIO来完成读写端口操作。什么是WINIO?WINIO是一个全免费的、无需注册的、含源程序的 WINDOWS2000端口操作驱动程序组件(可以到http://www.internals.com/上去下载)。它不仅可以操作端口,还可以操作内存;不仅能在VB下用,还可以在DELPHI、VC等其它环境下使用,性能特别优异。下载该组件,解压缩后可以看到几个文件夹,其中Release文件夹下的3个文件就是我们需要的,这3个文件是WinIo.sys(用于win xp下的驱动程序),WINIO.VXD(用于win 98下的驱动程序),WinIo.dll(封装函数的动态链接库),我们只需要调用WinIo.dll中的函数,然后WinIo.dll就会安装并调用驱动程序来完成相应的功能。值得一提的是这个组件完全是绿色的,无需安装,你只需要把这3个文件复制到与你的程序相同的文件夹下就可以使用了。用法很简单,先用里面的InitializeWinIo函数安装驱动程序,然后就可以用GetPortVal来读取端口或者用SetPortVal来写入端口了。好,让我们来做一个驱动级的键盘模拟吧。先把winio的3个文件拷贝到你的程序的文件夹下,然后在VB中新建一个工程,添加一个模块,在模块中加入下面的 winio函数声明:

Declare Function MapPhysToLin Lib "WinIo.dll" (ByVal PhysAddr As Long, ByVal PhysSize As Long, ByRef PhysMemHandle) As Long
Declare Function UnmapPhysicalMemory Lib "WinIo.dll" (ByVal PhysMemHandle, ByVal LinAddr) As Boolean
Declare Function GetPhysLong Lib "WinIo.dll" (ByVal PhysAddr As Long, ByRef PhysVal As Long) As Boolean
Declare Function SetPhysLong Lib "WinIo.dll" (ByVal PhysAddr As Long, ByVal PhysVal As Long) As Boolean
Declare Function GetPortVal Lib "WinIo.dll" (ByVal PortAddr As Integer, ByRef PortVal As Long, ByVal bSize As Byte) As Boolean
Declare Function SetPortVal Lib "WinIo.dll" (ByVal PortAddr As Integer, ByVal PortVal As Long, ByVal bSize As Byte) As Boolean
Declare Function InitializeWinIo Lib "WinIo.dll" () As Boolean
Declare Function ShutdownWinIo Lib "WinIo.dll" () As Boolean
Declare Function InstallWinIoDriver Lib "WinIo.dll" (ByVal DriverPath As String, ByVal Mode As Integer) As Boolean
Declare Function RemoveWinIoDriver Lib "WinIo.dll" () As Boolean

' ------------------------------------以上是WINIO函数声明-------------------------------------------

Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long

'-----------------------------------以上是WIN32 API函数声明-----------------------------------------

再添加下面这个过程:
Sub KBCWait4IBE()    '等待键盘缓冲区为空
Dim dwVal As Long
  Do
  GetPortVal &H64, dwVal, 1
'这句表示从&H64端口读取一个字节并把读出的数据放到变量dwVal中
'GetPortVal函数的用法是GetPortVal 端口号,存放读出数据的变量,读入的长度
  Loop While (dwVal And &H2)
End Sub
上面的是一个根据KBC规范写的过程,它的作用是在向键盘端口写入数据前等待一段时间,后面将会用到。
然后再添加如下过程,这2个过程用来模拟按键:

Public Const KBC_KEY_CMD = &H64    '键盘命令端口
Public Const KBC_KEY_DATA = &H60    '键盘数据端口

Sub MyKeyDown(ByVal vKeyCoad As Long) 
'这个用来模拟按下键,参数vKeyCoad传入按键的虚拟码
Dim btScancode As Long
btScancode = MapVirtualKey(vKeyCoad, 0)
 
    KBCWait4IBE    '发送数据前应该先等待键盘缓冲区为空
    SetPortVal KBC_KEY_CMD, &HD2, 1      '发送键盘写入命令
'SetPortVal函数用于向端口写入数据,它的用法是SetPortVal 端口号,欲写入的数据,写入数据的长度
    KBCWait4IBE
    SetPortVal KBC_KEY_DATA, btScancode, 1  '写入按键信息,按下键
   
End Sub

Sub MyKeyUp(ByVal vKeyCoad As Long) 
'这个用来模拟释放键,参数vKeyCoad传入按键的虚拟码
Dim btScancode As Long
btScancode = MapVirtualKey(vKeyCoad, 0)
    KBCWait4IBE    '等待键盘缓冲区为空
    SetPortVal KBC_KEY_CMD, &HD2, 1  '发送键盘写入命令
    KBCWait4IBE
    SetPortVal KBC_KEY_DATA, (btScancode Or &H80), 1  '写入按键信息,释放键
End Sub

定义了上面的过程后,就可以用它来模拟键盘输入了。在窗体模块中添加一个定时器控件,然后加入以下代码:

Private Sub Form_Load()

If InitializeWinIo = False Then 
  '用InitializeWinIo函数加载驱动程序,如果成功会返回true,否则返回false
    MsgBox "驱动程序加载失败!"
    Unload Me
End If
Timer1.Interval=3000
Timer1.Enabled=True
End Sub

Private Sub Form_Unload(Cancel As Integer)
ShutdownWinIo '程序结束时记得用ShutdownWinIo函数卸载驱动程序
End Sub

Private Sub Timer1_Timer()
Dim VK_A as Long = &H41
MyKeyDown VK_A   
MyKeyUp VK_A    '模拟按下并释放A键
End Sub

运行上面的程序,就会每隔3秒钟模拟按下一次A键,试试看,怎么样,是不是对所有程序都有效果了?
需要注意的问题:
要在VB的调试模式下使用WINIO,需要把那3个文件拷贝到VB的安装目录中。
键盘上有些键属于扩展键(比如键盘上的方向键就是扩展键),对于扩展键不应该用上面的MyKeyDown和MyKeyUp过程来模拟,可以使用下面的2个过程来准确模拟扩展键:
引用:
Sub MyKeyDownEx(ByVal vKeyCoad As Long)    '模拟扩展键按下,参数vKeyCoad是扩展键的虚拟码
Dim btScancode As Long
btScancode = MapVirtualKey(vKeyCoad, 0)

    KBCWait4IBE    '等待键盘缓冲区为空
    SetPortVal KBC_KEY_CMD, &HD2, 1      '发送键盘写入命令
    KBCWait4IBE
    SetPortVal KBC_KEY_DATA, &HE0, 1  '写入扩展键标志信息
   
   
    KBCWait4IBE    '等待键盘缓冲区为空
    SetPortVal KBC_KEY_CMD, &HD2, 1      '发送键盘写入命令
    KBCWait4IBE
    SetPortVal KBC_KEY_DATA, btScancode, 1  '写入按键信息,按下键
   
   
End Sub


Sub MyKeyUpEx(ByVal vKeyCoad As Long)    '模拟扩展键弹起
Dim btScancode As Long
btScancode = MapVirtualKey(vKeyCoad, 0)


    KBCWait4IBE    '等待键盘缓冲区为空
    SetPortVal KBC_KEY_CMD, &HD2, 1      '发送键盘写入命令
    KBCWait4IBE
    SetPortVal KBC_KEY_DATA, &HE0, 1  '写入扩展键标志信息
   
   
    KBCWait4IBE    '等待键盘缓冲区为空
    SetPortVal KBC_KEY_CMD, &HD2, 1      '发送键盘写入命令
    KBCWait4IBE
    SetPortVal KBC_KEY_DATA, (btScancode Or &H80), 1  '写入按键信息,释放键
   
End Sub
还应该注意的是,如果要从扩展键转换到普通键,那么普通键的KeyDown事件应该发送两次。也就是说,如果我想模拟先按下一个扩展键,再按下一个普通键,那么就应该向端口发送两次该普通键被按下的信息。比如,我想模拟先按下左方向键,再按下空格键这个事件,由于左方向键是扩展键,空格键是普通键,那么流程就应该是这样的:
MyKeyDownEx VK_LEFT    '按下左方向键
Sleep 200              '延时200毫秒
MyKeyUpEx VK_LEFT      '释放左方向键

Sleep 500
MyKeyDown VK_SPACE    '按下空格键,注意要发送两次
MyKeyDown VK_SPACE
Sleep 200
MyKeyUp VK_SPACE      '释放空格键


VB API初级入门(一)


三、如何才能提升你对API的学习兴趣?

  API,我常把它看做成过程函数,不过每人都有每人的见解和理解方式,自己的理解方式只要可以帮助自己更好的学习和掌握API,也没必要一定要学习他人的。

  1,自己做MsgBox

  了解API参数的使用方法是很重要的,这里我们不用VB的MsgBox,直接使用API弹出MsgBox消息框。首先,打开API浏览器,选择MessageBox,大家可以用这个API和VB内置的MsgBox比较一下,其实MsgBox也就是MessageBox的缩写,只不过一个是API,一个是VB内置的,但两者都是通过API进行工作的。好了,选择私有声明方式,粘贴到VB代码编辑窗口中,然后新建一个CommandButton,写入以下代码:

Private Declare Function MessageBox Lib "user32" Alias "MessageBoxA"
(ByVal hwnd As Long, ByVal lpText As String, ByVal lpCaption As String,
ByVal wType As Long) As Long
Private Sub Command1_Click()
    MessageBox Me.hwnd, "这里是内容", "标题", 0
End Sub

  先让我们来分析一下,首先看第一个参数Byval hWnd AsLong,很显然这是一个长整形变量,所以我们这里需要传递的是数字,你可能会发现我们传递的并不是数字啊,而是Me.hwnd??很奇怪是吗?如果你真的有此疑问说明你是真心想要学习好API的,现在就让我们来看看Me.hwnd到底是什么东西?以下摘自VB帮助文档:
  hWnd 属性:返回窗体或控件的句柄。
  句 柄:是由操作环境定义的一个唯一的整数值,它被程序用来标识或者切换到对象,如窗体或控件等。

  现在估计你差不多就已经明白了,我们调用的hwnd其实是一个句柄整数值,你可以用 Msgbox Me.hwnd 看一下就知道了。至于Me这是一个关键字,代表当前Form窗体对象。如:Me.Caption="标题"、Me.BackColor=vbRed等。

  接上面的,首先我们传入了Me.hwnd,表示是当前窗口调用MessageBox,这里告诉大家一个技巧,也就是以后凡是看到Byval hwnd As Long,一般都是需要传入句柄的,至于传入哪个对象句柄,那就要看你是怎么实现的了。
  ByVal lpText As String,这个是字符串变量,标识着叫我们需要传入字符串进去,可以看里面的变量字符lpText,属于文本的意思,也就是说是用来显示MsgBox中的消息文本的。
  ByVal lpCaption As String,也是字符串变量,还是传入字符串进去。在看里面的变量字符lpCaption,其实就是显示MsgBox标题的。
  ByVal wType AsLong,这是一个整形变量,需要传递整形数字,还是看里面的变量字符wType,标识着显示MsgBox类型,这里可以像VB的MsgBox一样使用,如这里可以传入:vbYesNo,vbOkCancel等,如果忽略那就传入0即可。

  好了,按F5启动程序,点击Command1,接着就会弹出一个消息框,这里我们制作以及分析MsgBox已经完成了。希望你能在这段学习到一些知识。

  2,来点实用的吧

  就拿隐藏Windows任务管理器来说吧,这里只能隐藏任务管理器中的窗口,不能隐藏进程。(问:有没有隐藏进程的?答:你想干什么?),当程序运行后你无法从任务管理器的窗口中关闭程序,只能从进程中进行终止。好了,还是老规矩,打开API浏览器,输入GetWindow和ShowWindow两个API,声明范围还是私有的,复制粘贴到Form代码窗口中,嗯,好了?别急,还是API浏览器,选择Combox中的常数,输入GW_OWNER和SW_HIDE这两个API常数,然后粘贴到代码窗口中,问我这两个是干什么的?那就接着往下看吧。写入以下代码:

Private Declare Function GetWindow Lib "user32" (ByVal hwnd As Long,
ByVal wCmd As Long) As Long
Private Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long,
ByVal nCmdShow As Long) As Long
Private Const GW_OWNER = 4
Private Const SW_HIDE = 0
Private Sub Form_Load()
    Dim lphWnd As Long
    lphWnd = GetWindow(Me.hwnd, GW_OWNER)
    ShowWindow lphWnd, SW_HIDE
End Sub
  先看GetWindow,表面意思:获取窗口。传递值变量:hWnd整形句柄,wCmd整形命令值。
  再看ShowWindow,表面意思:显示窗口。传递值变量:hWnd整形句柄,nCmdShow整形命令值。

  然后是使用代码,先看lphWnd = GetWindow(Me.hwnd,GW_OWNER)这句,这句意思是获取当前窗口的所有者窗口句柄,可以看到GetWindow是Function过程函数,执行以后会返回相应的窗口句柄值,这个值为Long整形(同句柄)。接着调用ShowWindow lphWnd,SW_HIDE,这句意思是显示lphwnd这个句柄的窗口,关键一句是最后的SW_HIDE,这是API函数的常量。通过设置常量能让系统知道API到底应该怎么执行显示窗口,是显示?还是隐藏?Hide当然是隐藏的意思。好了,编译成Exe,运行后打开任务管理器,查看程序窗口,还有吗?

  我又要说一下了,有些人可能不懂为什么要用GW_OWNER这些常量,这些到底有什么用?还有就是我怎么知道哪些API对应哪些的常量?其实这些常量你只要稍微注意一下就知道它们是怎么回事了,如在GetWindow中我使用GW_OWNER,在ShowWindow中我使用SW_HIDE这些常量都有一个共同的特点,就是他们都是以API的单词第一个字母为标准。如GetWindow相对应的常量就是Get(G)Window(W)=GW,ShowWindow相对应的常就是Show(S)Window(W)=SW,这些常量可以自己在VB的API浏览器中找找看。

    这次让我们来获取一下鼠标指针的位置。这里教大家一个技巧,当你想用API去实现某一特定的功能时,却又不知道该用哪个API,这时你可以就表面的意思到API浏览器找找,有70%以上的机率可以找到哦!现在就拿这个API开刀,那我们应该如何找?别着急,往下看:

    如我们现在要获取鼠标指针位置,可以这样翻译一下:Get(获取)Cursor(指针)Pos(位置),组合起来:GetCursorPos,呵呵,一条API就这样出来了,到API浏览器输入这个组合单词,呵,有吧?见以下:

Private Declare Function GetCursorPos Lib "user32" Alias "GetCursorPos" (lpPoint As POINTAPI) As Long

    lpPoint AsPOINTAPI,POINTAPI?很显然,在VB中并没有此类型,一般都是String、Integer、Long、Byte等变量类型,那么这个也就理所当然的是自定义类型(问:什么是自定义类型?答:不知道,自已不会看书啊)。既然是自定义类型,那么我们如何才能知道它是如何定义的呢?这里也就不用你操心啦,还是API浏览器,在最上面的Combox中选择类型,这时下面List中也就自然的把API的相关类型显示出来了,现在我们开始在Text文本框中输入我们需要的自定义类型,POINTAPI,点击添加,出来了吧?如下:

Private Type POINTAPI
        x As Long
        y As Long
End Type

    好了,现在开始写代码,添加一个Timer控件,设置属性见以下:

    Interval = 100
    Enabled  = True

    双击Timer控件,转到代码环境中写入以下代码:

Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Type POINTAPI
        x As Long
        y As Long
End Type
Private Sub Timer1_Timer()
    Dim lpPoint As POINTAPI
   
    GetCursorPos lpPoint
   
    Me.Caption = "X = " & lpPoint.x & " Y = " & lpPoint.y
End Sub

    好了,分析开始,紧张不?别紧张,没啥值得紧张的!见以下:

    Dim lpPoint As POINTAPI,申明一个POINTAPI类型变量,我们学过自定义类型的朋友都知道,一般使用自定义类型时都需要先申明一个相关的类型变量方可使用。

    GetCursorPoslpPoint,这一步我不说你都知道,调用API呗。通过这个API获取鼠标指针的相关信息。这里我们使用了自己声明的lpPoint变量,那为啥要使用这个变量呢?这里我们回过头来就前两节我们所分析的那样进行分析,可以看到GetCursorPos所需要传递的值,如果是Long,我们就传入整形数字,如果是String,我们就传入字符串,这里是POINTAPI,所以理所当然是要传入POINTAPI类型,但是!VB中的自定义类型不可以直接使用,所以我们需要先声明一个相同类型的变量。不知道说了这么多你懂了没?

 
    Me.Caption = "X = " & lpPoint.x & " Y = " &lpPoint.y,最后一句,也就是用来显示当前鼠标的坐标值的,我们通过声明的lpPoint变量来获取相应的鼠标坐标值,如果你不懂,那就请你先把VB自定义类型这章学完再说。

   
四、如何慢慢提升自己的API功力?
最后还是决定选择一个注销Windows程序来做题材(其实这是我当初学API最想实现的功能)。

    注销Windows也就是退出Windows(重启,关机等都一样,不都是退出的意思吗?),根据表面意思在API浏览器中输入Exit(退出)Windows,看看有没有这个API?这里提醒一下,你在查找这个API的时候还会看到ExitWindowsEX这个API,其实这两个API实现的功能一样,前者是用在16位操作系统上,只不过在Win32位操作系统上一般都使用ExitWindowsEX。所以这里就使用后者。API见以下:

 
  Private Declare Function ExitWindowsEx Lib "user32" Alias"ExitWindowsEx" (ByVal uFlags As Long, ByVal dwReserved As Long) As Long

    看看里面的两个参数,ByVal uFlags AsLong?这里我们需要传入一个整形数字,可是应该传入什么数字呢?这里说下,API中的参数可以传入不同的值,不同的值从而导致产生不同的结果。分析API中参数应该传递哪些值其实是有技巧的,以后大家只要是看到参数字符中包函Flags字符的话那就说明该参数可以被传入一个或多个标志,并且大部分都是传入API常数(什么是API常数就不用我说了吧)。说白话点,就是我们可以传入多个API常量,并且可以在API浏览器中找到,当然,不一定所有的API常数都可以在API浏览器中找到,不过大部分都可以。

 
    在API浏览器查API常量时我前面就教过大家技巧,现在该是我们实践的时候了,分析如下:

 
  Exit  :头一个大写字符 E
  Windows:头一个大写字符 W
  Ex    :头一个大写字符 E
  组合  :EWE_
 

    好了,现在在API浏览器的中常数中找找,咦?发现好像没有以EWE开头的常数??只发现以EWX开头的?现在先别着急,咱们回过头来再分析下,咱们是失败在最后一步Ex上,这里我不得不否决我前面教过大家的技巧,但是又不能完全否决,出现这种情况时就需要大家灵活运用API常数的分析法,可以看到EWX最后一个X是以Ex的X作结尾的,以这种方法做API常数开头的不止这一个,所以这里我特意留了一个陷阱,希望给大家带来一些经验将来能够灵活运用。现在我把关机uFlags所能用到的相关常数发上来,如下:

 
  Private Const EWX_FORCE = 4
  Private Const EWX_LOGOFF = 0
  Private Const EWX_REBOOT = 2
  Private Const EWX_SHUTDOWN = 1

    怎么样?看得懂吧?英语稍微好一点基本上没问题。不过这里我还是要解释一番,照顾新手嘛!

 
    EWX_FORCE 前面的 EWX_ 我就不说了,关键是看 _ 符号后面的,Force单词翻译:强制,强迫。人工在翻译一下(我英文不好,翻译错了请别见怪,呵呵 ^_^ ),意思是说:强制执行ExitWindowsExAPI关机函数。不知道这样解释你能不能明白。那到底这个常数有什么用呢?这里我们先回忆一下以前关机的时候,当Windows无法关闭某些窗口的时候就停止继续关机了,最后还得把无法关闭的窗口手动关闭方可,现在,如果我们使用这个常数进行关机,那Windows不管你窗口能不能关闭,直接强制关闭。希望你懂了。
 
  EWX_LOGOFF 这个嘛,貌似组合单词,不可直接翻译,那样就不是那个意思了。Logout Off,是这样写吗?注销的意思。
 
  EWX_REBOOT 不浪费时间了,直接说明意思:重新启动。
 
  EWX_SHUTDOWN 关机。

   
    至于第二个ByVal dwReserved AsLong,为保留整形,一般为0即可。至于为什么为0,大家可以到网上下载一些专门讲解API函数的电子书看看,里面有大部分API函数的详细讲解。或者下载VS.MSDN看看,在MSDN中说Windows 2000/95/98/Me中此参数忽略,XP中是指定关机消息说明。

 
    最后看看这个API为Function声明,说明该函数有返回值,返回值为Long,MSDN中说:如果执行成功,则返回非零,否则为零。

 
    现在上面已经把这个关机API和相关参数常量都给你分析透了,你可别告诉我你还不知道怎么写?好了,这里我们做一个定时注销程序,呵呵,虽然很简单,不过很多时候用得上哦!在Form窗口上添加Timer控件,Interval 设置为1000,Enabled 设置为 True。好了,代码如下:

 
  Private Declare Function ExitWindowsEx Lib "user32" (ByVal uFlags As Long, ByVal dwReserved As Long) As Long
  Private Const EWX_LOGOFF = 0
  Private Sub Timer1_Timer()
      Static i As Integer
   
      i = i + 1
   
      Me.Caption = i '这一步纯粹是想看看当前已经执行到几秒了?可不要
   
      If i = 10 Then '秒数判断,可以根据自己的需要进行运算
          ExitWindowsEx EWX_LOGOFF, 0
      End If
  End Sub

 
   
    2,先从一些最简单的API开始
 
    最简单的API,呵呵,哪些最简单呢?这个我也说不好,这样吧,咱们就从Get(获取)开始,那Get什么呢?Window(窗口),还是从窗口下手吧,这样更接近我们日常的编程,谁叫这是一个Windows操作系统呢?先列几个常用的API:

  GetWindow、GetWindowDC、GetWindowLong、GetWindowRect、GetWindowRgn、GetWindowsDirectory、GetWindowText、GetWindowThreadProcessId
 
    还有很多,我就先列举几个简单点的,咱们就从这几个中间随便抽几个来讲讲吧。
 
    先从GetWindowText下手,大家就表面的意思进行理解下,Get(获取)Window(窗口)Text(文本),VeryGood!这个API以前不错的,可以获取密码框中的密码,呵呵,说到这里,我估计有些人开始兴奋起来了!那好,Follow Me!
    新建一个Form窗口,然后添加一个CommandButton,Caption设置为:显示密码。接着添加两个TextBox,Text1属性设置:PasswordChar=*;Text=123456789,Text2的属性基本上没有什么需要设置的,只需要把Text属性为空就可以了,它主要是用来帮助咱们显示出密码的。好了,在Form1代码框中填入以下代码:
 
  Private Declare Function GetWindowText Lib "user32" Alias"GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByValcch As Long) As Long
  Private Sub Command1_Click()
      Dim sBuffer As String
   
      sBuffer = Space(255)
      GetWindowText Text1.hwnd, sBuffer, 255
   
      Text2.Text = sBuffer
  End Sub

 
    OK,F5运行,点击Command1,怎么样?Text1中的密码字符显示在Text2中了吧?你可以再更改下Text1中的密码,然后再点击Command1试试。也许你觉得会多此一举,为何不Text2.Text=Text1.Text这样?如果真的这样的话看似简单,那你就学不到API了。
 
    又到了开始分析的时候了,打起精神来,先看第一句:Dim sBuffer AsString,不用说,声明一个字符串变量呗!接着看第二句:sBuffer = Space(255)那这一句呢?有些人可能不知道了,没事,我会仔细讲的。Space是VB内置的字符串处理函数,VB中的帮助文件中有说明:
 
 
  开始{
 
  本示例使用 Space 函数来生成一个字符串,字符串的内容为空格,长度为指定的长度。
  Dim MyString
  ' 返回 10 个空格的字符串。
  MyString = Space(10)
  ' 将 10 个空格插入两个字符串中间。
  MyString = "Hello" & Space(10) & "World"
 
  }结束

很显然,我这一句是要分配255个空格字符串内存,为啥要用分配?这都是为后面所要用到打定的基础。接着往下:

 
    GetWindowText Text1.hwnd, sBuffer, 255这一步是关键,通过它来获取咱们想要的窗口文本,看第一个参数,我前面讲过hwnd一般都是需要传入句柄的,这时咱们传入了Text1.hwnd(Text1控件的句柄),第二个参数,lpSting为字符串变量,所以这里咱们传入sBuffer字符串变量。最后一个cch为Long整形,所以理应传入数字,这里我们传入了255。现在又有人想问了,为什么需要这么传入值?貌似和以前的传入不一样?确实!一刚开始你可能搞不懂,这时候我先讲讲大概的意思,我们用GetWindowText来获取窗口中的文本,当获取成功以后,理所当然会返回窗口中的字符串,但是当我们用这个API进行获取时,必须需要一个缓冲来保存我们所获取的字符串,你如果不信去试试把sBuffer =Space(255)去掉,后面的255其实就是告诉这个API我们缓冲字符串的大小,这里再告诉大家一个技巧,以后只要是看见包函有cch字符时,大部分都是输入相关类型的大小。
 

    再附加一点,就里我说过,hwnd是用来传句柄的,你也可以传入其它窗口句柄,只要其它窗口有文本,都是可以通过这个API获取的。还有Text2.Text = sBuffer其实是可以先把sBuffer处理一下再传给Text2.Text的,关于字符串处理这里不讲。

 
    好了,分析结束,来个小提示:在Windows操作系统中,任何有句柄的东东都可被看作为一个窗口。另外你可能会去试试QQ的密码框,^_^ 这里我要告诉你一下,无法成功,为什么无法成功呢?这是一个技术问题目前不提!

 
    接着再来试试GetWindowsDirectory,大家看表面意思吧!Get(获取)Windows(就是Windows目录)Directory(目录),也就是获取咱们那个系统目录,如:C:\Windows。可能我的Windows目录中在C盘,而其它人的可能在D盘、E盘也说不定,所以有的时候软件需要这个API进行获取操作系统具体的Windows目录。

 
    好了,还是新建一个标准EXE,添加一个CommandButton,属性Caption=显示Windows目录,OK,写入以下代码:

 
  Private Declare Function GetWindowsDirectory Lib "kernel32" Alias"GetWindowsDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long)As Long
  Private Sub Command1_Click()
      Dim sBuffer As String
   
      sBuffer = Space(255)
      GetWindowsDirectory sBuffer, 255
      MsgBox "Windows目录在: " & sBuffer
  End Sub
 

    分析!第一个Dim sBuffer As String字符串变量,sBuffer =Space(255)缓冲字符串,GetWindowsDirectory sBuffer,255这个和上面所讲的一样,最后一个参数nSize为Long整形,所以传入数值,那传入什么数值呢?Size???当然是缓冲字符串大小了,以后遇到这个nSize一般也是传入相关类型的大小的。MsgBox "Windows目录在: " &sBuffer,是用MsgBox消息框显示出Windows目录的位置。

        GetWindowThreadProcessId,这次玩玩窗口进程,我估计有些人只要看见与进程有关的东东也会变得兴奋,呵呵!好了,先看看这个API是什么样的?如下:

    Private Declare Function GetWindowThreadProcessId Lib "user32" Alias"GetWindowThreadProcessId" (ByVal hwnd As Long, lpdwProcessId As Long)As Long
 
    看表面意思:Get(获取)Window(窗口)Thread(线程)Process(程序)Id(ID),组合:获取当前线程的窗口进程ID。至于进程ID要着有什么用,自己以后深入32编程就知道了。

    看看参数,ByVal hwnd As Long,哈哈,熟悉吧,一个hWnd句柄。lpdwProcessId AsLong这个就是咱们需要的进程ID,老规矩,新建标准EXE,添加一个CommandButton,属性:Caption=获取窗口进程ID。代码如下:

    Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
  Private Sub Command1_Click()
      Dim PID As Long
   
      GetWindowThreadProcessId Me.hwnd, PID
   
      MsgBox "窗口进程的ID是:" & PID
  End Sub
 

    我已经习惯了给大家分析了。首先看看第一个参数,ByVal hwnd AsLong,又是句柄来的(问:废话!答:教会了你也别这样啊),lpdwProcessId AsLong,这个就要注意了,看看这个参数的传递方式,是以ByRef进行传递的(问:呵呵,不懂什么意思?答:不懂?转回去看过程函数这章),也就是说ByRef是以地址进行传递的,过程中可以改变传递的参数值。明白了吗?还不明白的话回去乖乖看书吧!现在明白了传递方式,也就是说我们声明的PID是用来获取窗口进程ID的,厉害啊。

     
    温馨小提示^_^:hWnd可以传入其它窗口句柄,同样可以获取其它窗口进程ID。

      接下来我们再来看看Set(设置),Set什么呢?当然还是Window(窗口)容易些,先列出几个常用的API:

      SetWindowLong、SetWindowPos、SetWindowRgn、SetWindowText
 
接上面的。

    首先咱们先看SetWindowText,咱们在上面讲过GetWindowText这个API,GetWindowText是用来获取窗口文本的,而这个正好相反。现在可以看看表面意思Set(设置)Window(窗口)Text(文本),好了这样理解就够了,我们已经知道这个API是设置窗口文本的,接着咱们就到API浏览器中找找这个API,如下:
 
    Private Declare Function SetWindowText Lib "user32" Alias"SetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String) As Long

    接着咱们看里面所需要传递的参数,一共有两,第一个ByVal hwnd As Long我就不用说了,传入句柄呗,第二个ByVallpString AsString,其中声明的lpString是字符串变量,可想而知,这里需要传入字符串,好了,开始实践。新建一个标准EXE,然后添加一个TextBox控件,然后再添加一个CommandButton,写入以下代码:

      Private Declare Function SetWindowText Lib "user32" Alias"SetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String) As Long

    Private Sub Command1_Click()
      SetWindowText Text1.hwnd, "这是咱们设置的文本"
    End Sub
 
    呵呵,这个看似比前面的更简单,不过我还是要罗嗦一下,首先把Text1的句柄传入第一个参数,这样API知道咱们需要操作哪个窗口,第二个是一个字符串变量,所以这里就是我们需要传入的文本。好了,F5运行,点击Command1,OK。
 
    再看SetWindowPos,可以说这个API可以看成设置窗口位置,但是最终的实现效果取决于咱们传递的参数,好了,在API浏览器中找到这个API,如下:
    Private Declare Function SetWindowPos Lib "user32" Alias"SetWindowPos" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long,ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long,ByVal wFlags As Long) As Long

    呵!好家伙,这个API看起来有些复杂啊?不过别担心,有我在嘛,我会帮你好好分析的,这里还请大家别光我一个人分析,必须把自己融入进来,咱们一起分析这样不更有趣?好了,废话少说,先看第一个参数:
 
  ByVal hwnd As Long  这里我就不讲了,传入窗口句柄
 
    ByVal hWndInsertAfter As Long好了,看看这个!hwndInstrAfter,可以看到里面包函有hwnd字符,这时你可能会说我前面不是已经说过嘛,只要看见包函有hwnd字符的都应该传入句柄嘛?呵呵,没错,你很聪明,记得我说的话呢!在这里夸一下你,别骄傲啊!现在咱们好好分析一下这个地方应该传入哪些参数!打开MSDN,不好意思是英文,这里我就把翻译过来的说明放上来,如下:
 
  hWndInsertAfter -  Long,窗口句柄。在窗口列表中,窗口hwnd会置于这个窗口句柄的后面。也可能选用下述值之一:
 
  HWND_BOTTOM    将窗口置于窗口列表底部

    HWND_TOP        将窗口置于Z序列的顶部;Z序列代表在分级结构中,窗口针对一个给定级别的窗口显示的顺序

    HWND_TOPMOST    将窗口置于列表顶部,并位于任何最顶部窗口的前面
 
  HWND_NOTOPMOST  将窗口置于列表顶部,并位于任何最顶部窗口的后面
 
    可以看到这个地方有四个参数供我们选择,一般我们会使用第三个API常数和第四个API常数,这几个API常数都可以在API浏览器中找到,至于具体实现什么功能我相信大家都知道吧,后面有写呢!

      再看看后面的几个 x,y,cx,cy 分别为Long变量,我上面讲过,SetWindowPos可以看成设置窗口位置嘛,所以这里理所当然是传入相关的坐标值,如果忽略则为0,自己可以试下。

      ByVal wFlags AsLong,这个参数,我又说过,看看字符Flags,呵呵,熟悉吧,所以这里咱们需要传入相关的标识常数,利用咱们以前学过的常数分析法进行分析,Set(S)Window(W)Pos(P)=SWP_ ,可以看到相关的常数了吧?这里我把相关常数的说明发上来大家看下,如下:

    SWP_DRAWFRAME    围绕窗口画一个框
 
  SWP_HIDEWINDOW  隐藏窗口
 
  SWP_NOACTIVATE  不激活窗口
 
  SWP_NOMOVE      保持当前位置(x和y设定将被忽略)
 
  SWP_NOREDRAW    窗口不自动重画
 
  SWP_NOSIZE      保持当前大小(cx和cy会被忽略)
 
  SWP_NOZORDER    保持窗口在列表的当前位置(hWndInsertAfter将被忽略)
 
  SWP_SHOWWINDOW  显示窗口
 
  SWP_FRAMECHANGED 强迫一条WM_NCCALCSIZE消息进入窗口,即使窗口的大小没有改变

    所以我说过,一个这样的API他具体实现的功能取决于你所传递的参数。假设这里咱们需要实现一个窗口永远置前的功能,首先新建一个标准EXE,输入以下代码:
    Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd AsLong, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long,ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
 Private Const HWND_TOPMOST = -1
 Private Const SWP_NOMOVE = &H2
 Private Const SWP_NOSIZE = &H1
 Private Sub Form_Load()
     SetWindowPos Me.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE
 End Sub

    现在咱们开始分析,第一个参数传入句柄,第二个我上面讲过,实现什么功能传入什么参数,这里咱们是实现的窗口永久置前的功能,所以传入HWND_TOPMOST常数,现在看看其实坐标,如果你不想改变窗口的具体位置的话,这里可不设为0,再看看后面的wFlags,我传入了两个常数,这两个常数的相关说明请大家看看上面就知道,主要是不改变窗口位置和不改变窗口大小的前提下把窗口置前,其它常数如果大家有兴趣可以自己试试。
 
    最后一个,看看SetWindowRgn,这里我要解释一番,这个API所实现的功能呢就是改变窗口外观,也就是咱们所说的异形窗口等,通过这个API咱们可以把窗口改变成任何形状,在API浏览器找到这个API,如下:
 
    Private Declare Function SetWindowRgn Lib "user32" Alias"SetWindowRgn" (ByVal hWnd As Long, ByVal hRgn As Long, ByVal bRedrawAs Boolean) As Long

    好了,第一个参数,句柄。第二个参数,Long变量,这里需要传入什么咱们下面会讲到。第三个,Boolean变量,可以说明这里需要传入布尔值,Redraw为重画的意思,所以如果我们用这个API改变窗口形状,这里需要为True,表示重画窗口。
 
    现在新建一个标准EXE,然后把Form的ScaleMode设置成3-Pixel,我们知道Windows是以像素为单位的,所以使用这个API进行设置的时候是以像素为单位进行处理窗口外观。然后把BorderStyle设置为0-None,这样看得更明显。好了,写入以下代码:

    Private Declare Function SetWindowRgn Lib "user32" (ByVal hWnd As Long, ByVal hRgn As Long, ByVal bRedraw As Boolean) As Long
  Private Declare Function CreateRoundRectRgn Lib "gdi32" (ByVal X1 AsLong, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal X3 AsLong, ByVal Y3 As Long) As Long
  Private Sub Form_Load()
      Dim hRgn As Long
   
      hRgn = CreateRoundRectRgn(0, 0, Me.ScaleWidth, Me.ScaleHeight, 10, 10)
      SetWindowRgn Me.hWnd, hRgn, True
  End Sub

    我不得不说一下这里我又用了一个API,主要是因为使用SetWindowRgn API是需要和其它API一起进行工作的,首先让我们先看看CreateRoundRectRgn这个API。分析如下:

      整体的意思是:创建圆角矩形。这里提示大家一个技巧,一般API中包函Rgn字符的都是代表可以改变对象外观的。可以看看我们使用的两个API,一个是SetWindowRgn(Rgn),一个是CreateRoundRectRgn(Rgn),希望你能明白其中的共同点。

  参数:x1,y1,x2,y2,x3,y3这些都是坐标值,具体说明见以下:
 
  X1,Y1 ----------  Long,矩形左上角的X,Y坐标
  X2,Y2 ----------  Long,矩形右下角的X,Y坐标
  X3 -------------  Long,圆角椭圆的宽。其范围从0(没有圆角)到矩形宽(全圆)
  Y3 -------------  Long,圆角椭圆的高。其范围从0(没有圆角)到矩形高(全圆)

    所以上面的代码具体是先通过CreateRoundRectRgn创建一个圆角矩形对象,然后通过SetWindowRgn来改变窗口的外观。
 
    小提示:使用CreateRoundRectRgn可以创建圆角矩形,也可以使用CreateEllipticRgn创建椭圆形,CreatePolyPolygonRgn创建多边形,CreateRectRgn矩形等,细心观察它们最后三个字符 Rgn 呵呵,明白了吧。

3,获取其它窗口的句柄
 
    这个我本来打算不讲的,不过网友们既然提出来了,我也只好详细说说。一般获取其它窗口的句柄使用以下API:
 
  FindWindow,FindWindowEx,WindowFromPoint
 
    这两个API就足矣,先看看第一个API的原型:

      Private Declare Function FindWindow Lib "user32" Alias"FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName AsString) As Long

    里面一共有两个参数,先看第一个:ByVal lpClassName AsString,字符串变量,所以这里需要传入字符串,第二个ByVal lpWindowName AsString,同样一个字符串变量,这里也需要传入字符串。再看这个API为Function,有返回值的,那返回值就是我们需要的句柄了。好了,现在了解了两个参数的具体传递类型,那我们现在就要知道这两个参数中到底应该传入哪些值?如下:
 
  ByVal lpClassName As String,lpClassName:类名。指窗口类名,如果忽略则传入vbNullString。
 
  ByVal lpWindowName As String,lpWindowName:窗口名称。指窗口文本,如果忽略则传入vbNullString。
 
    现在明白了两个参数需要传入哪些值就好办了,一个窗口的类名咱们有可能不知道,但是一个窗口的名称就好办了。如:咱们打开记事本程序,可以看到窗口标题显示为“无标题-记事本”。好了这就是咱们需要的,现在咱们就要通过这个窗口标题来获取记事本的句柄。新建一个标准EXE,然后输入以下代码:
 
  Private Declare Function FindWindow Lib "user32" Alias "FindWindowA"(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
  Private Sub Form_Load()
      Dim WindowHandle As Long
   
      WindowHandle = FindWindow(vbNullString, "无标题 - 记事本")
       
      MsgBox WindowHandle
  End Sub
 
    好了,F5运行,显示MsgBox消息框,如果不为0,那么咱们就获取成功了,如果为0,那么表示获取失败,这个时候你有必要检查一下你所要获取的窗口文本是否符合你所要获取的那个窗口文本(呵,这句话还真长!)。具体代码意思我就不讲了,大家可以自己分析下。

      小提示:这个时候咱们已经得到句柄了,具体得到这个句柄干什么?那就看你了。给个例子,如下:

      SetWindowText WindowHandle, "哈哈"
 
    看看把这个代码放在上面代码中试下,呵呵!注意,SetWindowText你要先声明这个API。别忘了。

      再看第二个FindWindowEx,这个API是在窗口列表中寻找与指定条件相符的第一个子窗口,原型如下:

Private Declare Function FindWindowEx Lib "user32" Alias"FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1As String, ByVal lpsz2 As String) As Long

    看里面的参数,第一个和第二个:ByVal hWnd1 As Long,ByVal hWnd2 AsLong,这里都需要传入句柄,再看第三个和第四个:ByVal lpsz1 As String, ByVal lpsz2 AsString,这里所要传入的是字符串。具体意思如下:

  hWnd1 ----------  Long,在其中查找子的父窗口。如设为零,表示使用桌面窗口(通常说的顶级窗口都被认为是桌面的子窗口,所以也会对它们进行查找)

  hWnd2 ----------  Long,从这个窗口后开始查找。这样便可利用对FindWindowEx的多次调用找到符合条件的所有子窗口。如设为零,表示从第一个子窗口开始搜索

  lpsz1 ----------  String,欲搜索的类名。零表示忽略,注意一般传入vbNullString

  lpsz2 ----------  String,欲搜索的类名。零表示忽略,注意一般传入vbNullString

    用实践帮我们分析,这里还是拿记事本开刀。打开一个记事本,新建一个标准EXE,接着新建一个CommandButton,Caption设置为:设置文本。OK,写入以下代码:
 
  Private Declare Function FindWindow Lib "user32" Alias "FindWindowA"(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
  Private Declare Function FindWindowEx Lib "user32" Alias"FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1As String, ByVal lpsz2 As String) As Long
  Private Declare Function EnableWindow Lib "user32" (ByVal hwnd As Long, ByVal fEnable As Long) As Long

  Private Sub Command1_Click()
      Dim WindowHandle As Long, ChildWindowHandle As Long
   
      WindowHandle = FindWindow(vbNullString, "无标题 - 记事本")
       
      If WindowHandle Then '如果获取句柄成功
               
          ChildWindowHandle = FindWindowEx(WindowHandle, 0, "Edit", vbNullString)
       
          If ChildWindowHandle Then '如果成功获取子句柄
              EnableWindow ChildWindowHandle, False '禁用子窗口
          Else
              MsgBox "无法获取子窗口"
          End If
      End If
  End Sub
 
    好了,帮大家分析。看第一行:Dim WindowHandle As Long, ChildWindowHandle AsLong,用于储存获取的句柄的。WindowHandle = FindWindow(vbNullString, "无标题 -记事本")这个就不用讲了,上面已经讲过。

      ChildWindowHandle = FindWindowEx(WindowHandle, 0, "Edit",vbNullString),这一段是通过我们已经获取的记事本句柄获取其中的子窗口句柄。大家可以用Spy++查看到记事本的TextBox类,然后根据类名写入即可。
 
    EnableWindow ChildWindowHandle, False这又是一个新的API,虽然前面我没有前过,但是这个API使用起来及其简单。这个API中有两个参数,第一个理所当然是传入窗口句柄,第二个为Long变量,其实这里应该设为Boolean变量好些,主要是用来处理当前窗口是否可用。True可用,False禁用。
 
    现在F5运行,记得打开记事本哦,然后点击Command1,看看能不能在记事本的文本框中输入字符串?是否被禁用了?
    小提示:EnableWindow之所有讲出来,是希望提高大家使用API的兴趣,有些被禁用的窗口你可以使用这个API把它激活,至于怎么使用就看你自己了,这里给大家布置一个作业,呵呵,自己去完成吧。

 
    最后一个API,WindowFromPoint,这个API主要是获取当前坐标的窗口句柄,不是有人想知道当前鼠标指针位置的窗口句柄吗?用这个是不错的选择,原型如下:
 
  Private Declare Function WindowFromPoint Lib "user32" Alias"WindowFromPoint" (ByVal xPoint As Long, ByVal yPoint As Long) As Long

    两个参数,一个是xPoint(x坐标值),一个是yPoint(y坐标值),现在你可以在这个两个参数分别传入其它窗口的坐标值就可以获取其它窗口的句柄了。可以看到为Function声明,返回值就是咱们需要的句柄。
 
    咱们想实现的功能是获取当前鼠标指针位置的句柄,所以这里当然需要用到GetCursorPos了,结合前面所讲的,新建一个标准EXE,添加一个Timer控件,Interval设置为100,Enabled=True,OK,写如以下代码:

    Private Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
  Private Type POINTAPI
          x As Long
          y As Long
  End Type
  Private Sub Timer1_Timer()
      Dim lpPoint As POINTAPI
      Dim WindowHandle As Long
   
      GetCursorPos lpPoint '获取当前鼠标指针坐标
      WindowHandle = WindowFromPoint(lpPoint.x, lpPoint.y)
   
      Me.Caption = "当前鼠标指针位置句柄:" & WindowHandle
  End Sub
 
    好了,最后一次给大家分析了,至于GetCursorPos的使用与说明前面已经讲过,这里不再分析。看看WindowHandle =WindowFromPoint(lpPoint.x,lpPoint.y)这句,它是通过GetCursorPos获取的鼠标坐标值获取当前鼠标坐标位置的句柄。最后一句我就不用说了,在程序窗口显示获取的句柄。
本帖最近评分记录:
  • 金钱:+5(lms7888) 对初学者有很大的好处
  • Hold infinity in the palm of your hand, and eternity in an hour
    顶端 Posted: 2008-03-05 08:18 | [楼 主]
    lms7888
    介就素人生啊
    级别: 论坛版主


    精华: 0
    发帖: 127
    威望: 10 点
    金钱: 2250 胜利币
    贡献值: 0 点
    在线时间:35(小时)
    注册时间:2007-05-19
    最后登录:2008-11-22

     

    可惜VB不能实现游戏界面下弹出.
    写修改器还是用VB吧.用.Net来写修改器有种高射炮打蚊子的感觉...
    顶端 Posted: 2008-03-05 18:27 | 1 楼
    帖子浏览记录 版块浏览记录
    胜利之歌超级论坛 » PC修改专区


    浙ICP备05022506号
    Total 0.556618(s) query 5, Time now is:11-23 04:33, Gzip enabled
    Powered by PHPWind v6.3.2 Certificate Code © 2003-08 PHPWind.com Corporation