模拟键盘.docx
《模拟键盘.docx》由会员分享,可在线阅读,更多相关《模拟键盘.docx(11页珍藏版)》请在冰豆网上搜索。
模拟键盘
∙[IOS]textField被虚拟键盘挡住解决方法
键盘是我们使用计算机的一个很重要的输入设备了,即使在鼠标大行其道的今天,很多程序依然离不开键盘来操作。
但是有时候,一些重复性的,很繁琐的键盘操作总会让人疲惫,于是就有了用程序来代替人们按键的方法,这样可以把很多重复性的键盘操作交给程序来模拟,省了很多精力,按键精灵就是这样的一个软件。
那么我们怎样才能用VB来写一个程序,达到与按键精灵类似的功能呢?
那就让我们来先了解一下windows中响应键盘事件的机制。
当用户按下键盘上的一个键时,键盘内的芯片会检测到这个动作,并把这个信号传送到计算机。
如何区别是哪一个键被按下了呢?
键盘上的所有按键都有一个编码,称作键盘扫描码。
当你按下一个键时,这个键的扫描码就被传给系统。
扫描码是跟具体的硬件相关的,同一个键,在不同键盘上的扫描码有可能不同。
键盘控制器就是将这个扫描码传给计算机,然后交给键盘驱动程序。
键盘驱动程序会完成相关的工作,并把这个扫描码转换为键盘虚拟码。
什么是虚拟码呢?
因为扫描码与硬件相关,不具有通用性,为了统一键盘上所有键的编码,于是就提出了虚拟码概念。
无论什么键盘,同一个按键的虚拟码总是相同的,这样程序就可以识别了。
简单点说,虚拟码就是我们经常可以看到的像VK_A,VK_B这样的常数,比如键A的虚拟码是65,写成16进制就是&H41,注意,人们经常用16进制来表示虚拟码。
当键盘驱动程序把扫描码转换为虚拟码后,会把这个键盘操作的扫描码和虚拟码还有其它信息一起传递给操作系统。
然后操作系统则会把这些信息封装在一个消息中,并把这个键盘消息插入到消息列队。
最后,要是不出意外的话,这个键盘消息最终会被送到当前的活动窗口那里,活动窗口所在的应用程序接收到这个消息后,就知道键盘上哪个键被按下,也就可以决定该作出什么响应给用户了。
这个过程可以简单的如下表示:
用户按下按键-----键盘驱动程序将此事件传递给操作系统-----操作系统将键盘事件插入消息队列-----键盘消息被发送到当前活动窗口
明白了这个过程,我们就可以编程实现在其中的某个环节来模拟键盘操作了。
在VB中,有多种方法可以实现键盘模拟,我们就介绍几种比较典型的。
1.局部级模拟:
-------------PostMessageA
-------------SendMessageA
-------------MapVirtualKeyA
从上面的流程可以看出,键盘事件是最终被送到活动窗口,然后才引起目标程序响应的。
那么最直接的模拟方法就是:
直接伪造一个键盘消息发给目标程序。
哈哈,这实在是很简单,windows提供了几个这样的API函数可以实现直接向目标程序发送消息的功能,常用的有SendMessage和PostMessage,它们的区别是PostMessage函数直接把消息仍给目标程序就不管了,而SendMessage把消息发出去后,还要等待目标程序返回些什么东西才好。
这里要注意的是,模拟键盘消息一定要用PostMessage函数才好,用SendMessage是不正确的(因为模拟键盘消息是不需要返回值的,不然目标程序会没反应),切记切记!
PostMessage函数的VB声明如下:
DeclareFunctionPostMessageLib"user32"Alias"PostMessageA"(ByValhwndAsLong,ByValwMsgAsLong,ByValwParamAsLong,lParamAsAny)AsLong
参数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个字节,写成二进制就是00000000000000000000000000000000 一共是32位,我们从右向左数,假设最右边那位为第0位(注意是从0而不是从1开始计数),最左边的就是第31位,那么该参数的的0-15位表示键的发送次数等扩展信息,16-23位为按键的扫描码,24-31位表示是按下键还是释放键。
大家一般习惯写成16进制的,那么就应该是&H00000000,第0-15位一般为&H0001,如果是按下键,那么24-31位为&H00,释放键则为&HC0,那么16-23位的扫描码怎么会得呢?
这需要用到一个API函数MapVirtualKey,这个函数可以将虚拟码转换为扫描码,或将扫描码转换为虚拟码,还可以把虚拟码转换为对应字符的ASCII码。
它的VB声明如下:
DeclareFunctionMapVirtualKeyLib"user32"Alias"MapVirtualKeyA"(ByValwCodeAsLong,ByValwMapTypeAsLong)AsLong
参数wCode表示待转换的码,参数wMapType表示从什么转换为什么,如果是虚拟码转扫描码,则wMapType设置为0,如果是虚拟扫描码转虚拟码,则wMapType设置为1,如果是虚拟码转ASCII码,则wMapType设置为2.相信有了这些,我们就可以构造键盘事件的lParam参数了。
下面给出一个构造lParam参数的函数:
DeclareFunctionMapVirtualKeyLib"user32"Alias"MapVirtualKeyA"(ByValwCodeAsLong,ByValwMapTypeAsLong)AsLong
FunctionMakeKeyLparam(ByValVirtualKeyAsLong,ByValflagAsLong)AsLong
'参数VirtualKey表示按键虚拟码,flag表示是按下键还是释放键,用WM_KEYDOWN和WM_KEYUP这两个常数表示
DimsAsString
DimFirstbyteAsString 'lparam参数的24-31位
Ifflag=WM_KEYDOWN Then'如果是按下键
Firstbyte="00"
Else
Firstbyte="C0" '如果是释放键
EndIf
DimScancodeAsLong
'获得键的扫描码
Scancode=MapVirtualKey(VirtualKey,0)
DimSecondbyteAsString 'lparam参数的16-23位,即虚拟键扫描码
Secondbyte=Right("00"&Hex(Scancode),2)
s=Firstbyte&Secondbyte&"0001" '0001为lparam参数的0-15位,即发送次数和其它扩展信息
MakeKeyLparam=Val("&H"&s)
EndFunction
这个函数像这样调用,比如按下A键,那么lParam=MakeKeyLparam(VK_A,WM_KEYDOWN),很简单吧。
值得注意的是,即使你发送消息时设置了lParam参数的值,但是系统在传递消息时仍然可能会根据当时的情况重新设置该参数,那么目标程序收到的消息中lParam的值可能会和你发送时的有所不同。
所以,如果你很懒的话,还是直接把它设为0吧,对大多数程序不会有影响的,呵呵。
好了,做完以上的事情,现在我们可以向目标程序发送键盘消息了。
首先取得目标程序接受这个消息的控件的句柄,比如目标句柄是12345,那么我们来对目标模拟按下并释放A键,像这样:
(为了简单起见,lParam这个参数就不构造了,直接传0)
PostMessage12345,WM_KEYDOWN,VK_A,0& '按下A键
PostMessage12345,WM_UP,VK_A,0& '释放A键
如果要向目标程序发送字符,光靠WM_KEYDOWN和WM_UP这两个事件还不行,还需要一个事件:
WM_CHAR,这个消息表示一个字符,程序需靠它看来接受输入的字符。
一般只有A,B,C等这样的按键才有WM_CHAR消息,别的键(比如方向键和功能键)是没有这个消息的,WM_CHAR消息一般发生在WM_KEYDOWN消息之后。
WM_CHAR消息的lParam参数的含义与其它键盘消息一样,而它的wParam则表示相应字符的ASCII编码(可以输入中文的哦^_^),现在你可以写出一个完整的向记事本里自动写入字符的程序了,下面是一个例子,并附有这些消息常数的具体值:
DeclareFunctionPostMessageLib"user32"Alias"PostMessageA"(ByValhwndAsLong,ByValwMsgAsLong,ByValwParamAsLong,lParamAsAny)AsLong
DeclareFunctionMapVirtualKeyLib"user32"Alias"MapVirtualKeyA"(ByValwCodeAsLong,ByValwMapTypeAsLong)AsLong
PublicConstWM_KEYDOWN=&H100
PublicConstWM_KEYUP=&H101
PublicConstWM_CHAR=&H102
PublicConstVK_A=&H41
FunctionMakeKeyLparam(ByValVirtualKeyAsLong,ByValflagAsLong)AsLong
DimsAsString
DimFirstbyteAsString 'lparam参数的24-31位
Ifflag=WM_KEYDOWN Then'如果是按下键
Firstbyte="00"
Else
Firstbyte="C0" '如果是释放键
EndIf
DimScancodeAsLong
'获得键的扫描码
Scancode=MapVirtualKey(VirtualKey,0)
DimSecondbyteAsString 'lparam参数的16-23位,即虚拟键扫描码
Secondbyte=Right("00"&Hex(Scancode),2)
s=Firstbyte&Secondbyte&"0001" '0001为lparam参数的0-15位,即发送次数和其它扩展信息
MakeKeyLparam=Val("&H"&s)
EndFunction
PrivateSubForm_Load()
dimhwndaslong
hwnd=XXXXXX 'XXXXX表示记事本编辑框的句柄
PostMessagehwnd,WM_KEYDOWN,VK_A,MakeKeyLparam(VK_A,WM_KEYDOWN) '按下A键
PostMessagehwnd,WM_CHAR,ASC("A"),MakeKeyLparam(VK_A,WM_KEYDOWN) '输入字符A
PostMessagehwnd,WM_UP,VK_A,MakeKeyLparam(VK_A,WM_UP) '释放A键
EndSub
这就是通过局部键盘消息来模拟按键。
这个方法有一个极大的好处,就是:
它可以实现后台按键,也就是说他对你的前台操作不会有什么影响。
2.全局级模拟
-------------keybd_event
-------------SendInput
-------------SetWindowsHookExA
你会发现,用上面的方法模拟按键并不是对所有程序都有效的,有的程序啊,你向它发了一大堆消息,可是它却一点反应也没有。
这是怎么回事呢?
这就要看具体的情况了,有些程序(特别是一些游戏)出于某些原因,会禁止用户对它使用模拟按键程序,这个怎么实现呢?
比如可以在程序中检查一下,如果发现自己不是活动窗口,就不接受键盘消息。
或者仔细检查一下收到的键盘消息,你会发现真实的按键和模拟的按键消息总是有一些小差别,从这些小差别上,目标程序就能判断出:
这是假的!
是伪造的!
!
因此,如果用PostMessage发送局部消息模拟按键不成功的话,你可以试一试全局级的键盘消息,看看能不能骗过目标程序。
模拟全局键盘消息常见的可以有以下一些方法:
(1)用API函数keybd_event,这个函数可以用来模拟一个键盘事件,它的VB声明为:
DeclareSubkeybd_eventLib"user32"(ByValbVkAsByte,ByValbScanAsByte,ByValdwFlagsAsLong,ByValdwExtraInfoAsLong)
参数bVk表示要模拟的按键的虚拟码,bScan表示该按键的扫描码(一般可以传0),dwFlags表示是按下键还是释放键(按下键为0,释放键为2),dwExtraInfo是扩展标志,一般没有用。
比如要模拟按下A键,可以这样:
ConstKEYEVENTF_KEYUP=&H2
keybd_eventVK_A,0,0,0 '按下A键
keybd_eventVK_A,0,KEYEVENTF_KEYUP,0 '释放A键
注意有时候按键的速度不要太快,否则会出问题,可以用API函数Sleep来进行延时,声明如下:
DeclareSubSleepLib"kernel32"(ByValdwMillisecondsAsLong)
参数dwMilliseconds表示延时的时间,以毫秒为单位。
那么如果要模拟按下功能键怎么做呢?
比如要按下Ctrl+C实现拷贝这个功能,可以这样:
keybd_eventVK_Ctrl,0,0,0 '按下Ctrl键
keybd_eventVK_C,0,0,0 '按下C键
Sleep500 '延时500毫秒
keybd_eventVK_C,0,KEYEVENTF_KEYUP,0 '释放C键
keybd_eventVK_Ctrl,0,KEYEVENTF_KEYUP,0 '释放Ctrl键
好了,现在你可以试试是不是可以骗过目标程序了,这个函数对大部分的窗口程序都有效,可是仍然有一部分游戏对它产生的键盘事件熟视无睹,这时候,你就要用上bScan这个参数了。
一般的,bScan都传0,但是如果目标程序是一些DirectX游戏,那么你就需要正确使用这个参数传入扫描码,用了它可以产生正确的硬件事件消息,以被游戏识别。
这样的话,就可以写成这样:
keybd_eventVK_A,MapVirtualKey(VK_A,0),0,0 '按下A键
keybd_eventVK_A,MapVirtualKey(VK_A,0),KEYEVENTF_KEYUP,0 '释放A键
以上就是用keybd_event函数来模拟键盘事件。
除了这个函数,SendInput函数也可以模拟全局键盘事件。
SendInput可以直接把一条消息插入到消息队列中,算是比较底层的了。
它的VB声明如下:
DeclareFunctionSendInputLib"user32.dll"(ByValnInputsAsLong,pInputsAsGENERALINPUT,ByValcbSizeAsLong)AsLong
参数:
nlnprts:
定义plnputs指向的结构的数目。
plnputs:
指向INPUT结构数组的指针。
每个结构代表插人到键盘或鼠标输入流中的一个事件。
cbSize:
定义INPUT结构的大小。
若cbSize不是INPUT结构的大小,则函数调用失败。
返回值:
函数返回被成功地插人键盘或鼠标输入流中的事件的数目。
若要获得更多的错误信息.可以调用GetlastError函数。
备注:
Sendlnput函数将INPUT结构中的事件顺序地插入键盘或鼠标的输入流中。
这些事件与用户插入的(用鼠标或键盘)或调用keybd_event,mouse_event,或另外的Sendlnput插人的键盘或鼠标的输入流不兼容。
嗯,这个函数用起来蛮复杂的,因为它的参数都是指针一类的东西。
要用它来模拟键盘输入,先要构造一组数据结构,把你要模拟的键盘消息装进去,然后传给它。
为了方便起见,把它做在一个过程里面,要用的时候直接调用好了,代码如下:
DeclareFunctionSendInputLib"user32.dll"(ByValnInputsAsLong,pInputsAsGENERALINPUT,ByValcbSizeAsLong)AsLong
DeclareSubCopyMemoryLib"kernel32"Alias"RtlMoveMemory"(pDstAsAny,pSrcAsAny,ByValByteLenAsLong)
TypeGENERALINPUT
dwTypeAsLong
xi(0To23)AsByte
EndType
TypeKEYBDINPUT
wVkAsInteger
wScanAsInteger
dwFlagsAsLong
timeAsLong
dwExtraInfoAsLong
EndType
ConstINPUT_KEYBOARD=1
SubMySendKey(bkeyAsLong)
'参数bkey传入要模拟按键的虚拟码即可模拟按下指定键
DimGInput(0To1)AsGENERALINPUT
DimKInputAsKEYBDINPUT
KInput.wVk=bkey '你要模拟的按键
KInput.dwFlags=0'按下键标志
GInput(0).dwType=INPUT_KEYBOARD
CopyMemoryGInput(0).xi(0),KInput,Len(KInput)'这个函数用来把内存中KInput的数据复制到GInput
KInput.wVk=bkey
KInput.dwFlags=KEYEVENTF_KEYUP '释放按键
GInput
(1).dwType=INPUT_KEYBOARD'表示该消息为键盘消息
CopyMemoryGInput
(1).xi(0),KInput,Len(KInput)
'以上工作把按下键和释放键共2条键盘消息加入到GInput数据结构中
SendInput2,GInput(0),Len(GInput(0)) '把GInput中存放的消息插入到消息列队
EndSub
除了以上这些,用全局钩子也可以模拟键盘消息。
如果你对windows中消息钩子的用法已经有所了解,那么你可以通过设置一个全局HOOK来模拟键盘消息,比如,你可以用WH_JOURNALPLAYBACK这个钩子来模拟按键。
WH_JOURNALPLAYBACK是一个系统级的全局钩子,它和WH_JOURNALRECORD的功能是相对的,常用它们来记录并回放键盘鼠标操作。
WH_JOURNALRECORD钩子用来将键盘鼠标的操作忠实地记录下来,记录下来的信息可以保存到文件中,而WH_JOURNALPLAYBACK则可以重现这些操作。
当然亦可以单独使用WH_JOURNALPLAYBACK来模拟键盘操作。
你需要首先声明SetWindowsHookEx函数,它可以用来安装消息钩子:
DeclareFunctionSetWindowsHookExLib"user32"Alias"SetWindowsHookExA"(ByValidHookAsLong,ByVallpfnAsLong,ByValhmodAsLong,ByValdwThreadIdAsLong)AsLong
先安装WH_JOURNALPLAYBACK这个钩子,然后你需要自己写一个钩子函数,在系统调用它时,把你要模拟的事件传递给钩子参数lParam所指向的EVENTMSG区域,就可以达到模拟按键的效果。
不过用这个钩子模拟键盘事件有一个副作用,就是它会锁定真实的鼠标键盘,不过如果你就是想在模拟的时候不会受真实键盘操作的干扰,那么用用它倒是个不错的主意。
3.驱动级模拟:
如果上面的方法你都试过了,可是你发现目标程序却仍然顽固的不接受你模拟的消息,寒~~~~~~~~~还好,我还剩下最后一招,这就是驱动级模拟:
直接读写键盘的硬件端口!
有一些使用DirectX接口的游戏程序,它们在读取键盘操作时绕过了windows的消息机制,而使用DirectInput.这是因为有些游戏对实时性控制的要求比较高,比如赛车游戏,要求以最快速度响应键盘输入。
而windows消息由于是队列形式的,消息在传递时会有不少延迟,有时1秒钟也就传递十几条消息,这个速度达不到游戏的要求。
而DirectInput则绕过了windows消息,直接与键盘驱动程序打交道,效率当然提高了不少。
因此也就造成,对这样的程序无论用PostMessage或者是keybd_event都不会有反应,因为这些函数都在较高层。
对于这样的程序,只好用直接读写键盘端口的方法来模拟硬件事件了。
要用这个方法来模拟键盘,需要先了解一下键盘编程的相关知识。
在DOS时代,