windows模拟键盘鼠标事件DirectX游戏中_Discovery的博客-程序员宅基地_如何在directx游戏模拟键盘

技术标签: Kinect  扩展  C++  function  游戏  integer  cmd  windows  

有一些使用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 '释放空格键 

好了,相信到这里,你的模拟按键程序也就差不多了,测试一下,是不是很有效呢,嘿嘿~~~~ 
WINIO组件的下载地址:http://www.114vip.com.cn/download/winio.zip 

以下是用winio模拟鼠标的函数,但是并不是在所有系统中都能正常运行,有需要的试试看吧 
Sub MyMouseKey(MouseFun As Long, MyMouseX As Long, MyMouseY As Long, MyMouseZ As Long) 
' 左键按下(MouseFun=9)。MyMouseX、MyMouseY、MyMouseZ 为0 
' 右键按下(MouseFun=10)。MyMouseX、MyMouseY、MyMouseZ 为0 
' 中键按下(MouseFun=12)。MyMouseX、MyMouseY、MyMouseZ 为0 
' 任意键放开(MouseFun=8)。MyMouseX、MyMouseY、MyMouseZ 为0 
' ------------------------------------ 
' 鼠标上移(MouseFun=8)。MyMouseY为移动距离,最大为255,最小为1。MyMouseX、MyMouseZ 为0 
' 鼠标下移(MouseFun=40)。MyMouseY为移动距离,最大为1,最小为255。MyMouseX、MyMouseZ 为0 
' 鼠标左移(MouseFun=24)。MyMouseX为移动距离,最大为1,最小为255。MyMouseY、MyMouseZ 为0 
' 鼠标右移(MouseFun=8)。MyMouseX为移动距离,最大为255,最小为1。MyMouseY、MyMouseZ 为0 
' ------------------------------------ 
KBCWait4IBE '等待缓冲区为空 
SetPortVal 100, 211, 1 '发送鼠标写入命令 
KBCWait4IBE '等待缓冲区为空 
SetPortVal 96, MouseFun, 1 '发送鼠标动作命令 
'------------------------------------- 
KBCWait4IBE '等待缓冲区为空 
SetPortVal 100, 211, 1 '发送鼠标写入命令 
KBCWait4IBE '等待缓冲区为空 
SetPortVal 96, MyMouseX, 1 '发送鼠标动作命令 
'------------------------------------- 
KBCWait4IBE '等待缓冲区为空 
SetPortVal 100, 211, 1 '发送鼠标写入命令 
KBCWait4IBE '等待缓冲区为空 
SetPortVal 96, MyMouseY, 1 '发送鼠标动作命令 
'------------------------------------- 
KBCWait4IBE '等待缓冲区为空 
SetPortVal 100, 211, 1 '发送鼠标写入命令 
KBCWait4IBE '等待缓冲区为空 
SetPortVal 96, MyMouseZ, 1 '发送鼠标动作命令 
End Sub 


示例: 
MyMouseKey 9, 0, 0, 0 '左键按下 
MyMouseKey 8, 0, 0, 0 '左键放开 
MyMouseKey 10, 0, 0, 0 '右键按下 
MyMouseKey 8, 0, 0, 0 '右键放开 
MyMouseKey 12, 0, 0, 0 '中键按下 
MyMouseKey 8, 0, 0, 0 '中键放开 
MyMouseKey 8, 0, 5, 0 '上移5象素 
MyMouseKey 40, 0,(255 xor 5),0 '下移5象素 
MyMouseKey 24,(255 xor 5), 0, 0 '左移5象素 
MyMouseKey 8, 5, 0, 0 '右移5象素


引文来源  http://hi.baidu.com/zhaocs/blog/item/af15c162322fb96a0c33fa50.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yanxin007/article/details/7846391

智能推荐

6.eureka集群搭建及服务注册_tengqingshan110的博客-程序员宅基地

一、创建eureka集群1.创建modul另创建两个与microservice-eureka2001一样的模块为microservice-eureka2002、microservice-eureka20032.修改配置(1)修改端口把microservice-eureka2002的端口改为2002microservice-eureka2002端口改为2003(2)修改链接路径mic...

工具类 - jetson录屏工具和串口调试工具_Gaosiy的博客-程序员宅基地

录屏工具vokoscreen安装sudo apt-get install vokoscreen运行vokoscreen带界面的串口调试工具cutecom安装sudo apt-get install cutecom运行cutecom

Android对象序列化的坑_青梅煮茶1111的博客-程序员宅基地

客户端使用的AS开发,因为AS默认的包名带有一大串,所以在序列化的时候导致服务器接受出错服务器端的格式客户端(Android)端的格式在运行的时候,服务器端接收消息的时候,报了错误java.lang.ClassNotFoundException: com.example.mryan.socketstest.Message.Info1这个错误就表示...

HttpServletRequest获取远程客户端IP(考虑代理)以及java获取本机IP地址(去除虚拟机、蓝牙等)_初心JAVA的博客-程序员宅基地_httpservletrequest 得到远程地址

protected String getRemoteIP() { String ip = null; // X-Forwarded-For:Squid 服务代理 String ipAddresses = request.getHeader("X-Forwarded-For"); if (ipAddresses == null || ipAddresses.length() ==...

Matlab通过JDBC连接SQLite数据库_silenceshining的博客-程序员宅基地

http://blog.macro2.org/2011-03/110327matlab通过jdbc连接sql-server.html1.下载用于连接SQLite的JDBC驱动。地址:https://cn.jarfire.org/sqlitejdbc.html2.向Matlab的ClassPath.txt文件中添加sqlitejdbc-v056.jar的路径在Matlab中加入j

C#截屏代码_上海无心的博客-程序员宅基地_c#截图代码

private void bt_cutScreen_Click(object sender, EventArgs e) { Screen sc = Screen.PrimaryScreen;//获取屏幕分辨率 Rectangle rc = sc.Bounds; int iWidth = rc.Width;

随便推点

poj2079——旋转卡壳求凸包最大三角形面积_mumei314的博客-程序员宅基地

题目链接:http://poj.org/problem?id=2079Given n distinct points on a plane, your task is to find the triangle that have the maximum area, whose vertices are from the given points.InputThe input consi...

国家信息安全事件加速“去IOE"--零一拼装推出中国人自己的顶尖软件白盒测试工具..._threadingtest2014的博客-程序员宅基地

软件测试工具,作为一个软件的细分领域,它和基础类的操作系统一样,一直是国人难以触及的技术领域,它的研发需要对软件语言最底层的编译技术以及软件测试方法有非常深入的技术储备。软件测试工具,尤其是可以达到商用标准的白盒测试工具一直被国外的几大老牌软件测试工具所垄断,即使中国的军方以及航天航空领域也通常别无选择。在斯诺登事件发生以后,信息安全问题已经上升到国家安全的高度,并且随着数字化、网络化、智能化、...

史海峰:构建产业互联网金融系统的正确姿势_普通网友的博客-程序员宅基地

史海峰IT民工闲话读完需要12分钟速读仅需 5 分钟引言 互联网下半场从 ToC 进入 ToB 阶段,玩法不再是烧钱拉流量转化变现,而是深入产业核心领域,通过技术提升生态链整合能力,...

做个高颜值的优秀按钮,用 CSS3 实现社交按钮动画_蓝桥云课的博客-程序员宅基地

在过去,产品只要把功能做到位就足够了,而现在,产品不仅要有好的颜值,还要有良好的交互和动效。一个优秀的动效不仅可以吸引用户的眼球,还可以让产品更具交互性,吸引更多用户的使用。比如,按钮(button)可能是网页中最常见的组件之一了,大家看到了也不会有多大的兴趣,但如果你碰到的是一个这样的按钮,会不会忍不住多点几次呢?今天就给大家带来一门新课——《CSS3 实现社交分享按钮动画》,教大家用 CSS3 实现按钮的动态效果~限时免费哦!CSS 可以做许多美化和布局工作,课程通过导入 Font Aweso

C# 操作Excel (NPIO实例)_小白卟白的博客-程序员宅基地_npio

一 需要引用using NPOI.HSSF.UserModel;using NPOI.SS.UserModel;//创建excel HSSFWorkbook hwb = new HSSFWorkbook();//添加sheet页 ISheet sheet = hwb.CreateSheet(); IRow row; ICell cell;for (int i = 0; i < 10; i++) { //行

资料同化简介及GSI同化系统的安装_气海同途的博客-程序员宅基地

背景介绍气象学家Bjerknes曾把数值预报可归结为初值问题,假设模式完美,给定合适的初始状态和边界条件,向前积分数值模式可得到未来时刻的预报信息。大气状态是非线性的,对初值极其敏感,即使数值模式是完美的且收敛于真实状态,初始状态的微小改变,积分后误差也会变得很大。因此,初始场的准确度直接关系到数值模式的预报效果,为得到良好的数值预报结果,数十年来气象学家一直致力于发展能够提高和改善初始场的技术,即资料同化技术。资料同化的参考资料,可以关注气海同途公众号后台回复关键字资料同化,获取以下学习资料。

推荐文章

热门文章

相关标签