[原创]Delphi 下利用 WinIo 模拟鼠标键盘详解
原创]
Delphi 前言 一日发现 SendInput 对某程序居然无效,无奈只好开始研究 WinIo.上网查了很多资料,发现关于 Wi nIo 模拟鼠标键盘的资料很少,有的也只是支言片语讲的不是很详细,而且大部分都是关于模拟键盘的. 自己写了一些程序研究一方,经历了无数次的键盘死锁,鼠标满屏乱飞,复位重启,总算小有结果.现在 将研究结果写出来与大家分享.另外,本人的水平有限文中有出错的地方欢迎根贴讨论.(PS:关于 Send Input 的使用可以参考我写的另一篇贴子《Delphi 下利用 SendInput 模拟鼠标键盘》programbbs. com/
bbs/view12-17219-1.htm) 我已经将主要的模拟功能写在了一个单元文件中:MouseKeyboard.pas,调用该单元文件中的相关函数 就可以实现鼠标键盘的模拟.该单元文件的下载和使用方法请参考 2 楼的内容.在本楼的末尾有一个中英 文对译 PS/2 鼠标键盘协议的下载,这个协议对编写模拟鼠标键盘的程序很有帮助.另外我还提供了一个鼠 标移动速度测试程序,一个使用 MouseKeyboard.pas 的简单示范程序的下载. 一,WinIo 简介 WinIo 通过加载一个内核模式的设备驱动程序,利用几种底层编程技巧,使得 Windows 应用程序可以 直接对 I/O 端口和物理内存进行存取,从而绕过了 Windows 系统的保护机制.WinIo 包含了 3 个文件:Win Io.dll,WinIo.sys 和 WINIO.VXD,其中 WINIO.VXD 驱动程序用在 Win95/98
系统上,WinIo.sys 驱动
程序 用在 WinNT/2000/XP 系统上,WinIo.dll 提供了功能函数的调用.在 WinIo.dll 中有两个函数最重要:Ini tializeWinIo 用来初始化 WinIo 的驱动程序,必须在调用所有其它功能函数之前调用该函数;ShutdownWi nIo 用来卸载 WinIo 的驱动程序,在中止应用函数之前或者不再需要使用 WinIo 时调用.在初始化完成之 后就可以直接读写 I/O 端口而不会出现非法操作,本程序就是利用向鼠标键盘硬件端口写入数据来模拟鼠 标键盘的操作. 由于是底层的硬件端口读写,所以必需对硬件的相关协议有所了解,对于鼠标键盘最重要的协议就是 PS/2 鼠标键盘协议(以下简称 PS/2 协议).我这里提供了一个 pdf 版的中英文对译 PS/2 鼠标键盘协议, 在该协议的前半部主要讲硬件的电气接口协议,但是后半部分的内容对于模拟鼠标键盘非常有用.这个协 议可是我在网上翻腾了好久才找到的.协议的下载见本楼末尾. 二,Intel 8042 Intel 8042 或兼容微控制器(以下简称 i8042)被用作 PC 键盘的控制器,虽然名为键盘控制器,但是 实际上鼠标也是由其控制的.i8042 一般整合在芯片组中.向 i8042 发送指定的命令和数据就可以模拟鼠 标键盘的操作.i8042 包含了如下四个寄存器: 一个字节的输入缓冲区:包含从鼠标或键盘读入的字节;只读. 一个字节
的输出缓冲区:包含要写到鼠标或键盘的字节;只写. 一个字节的状态寄存器:8 个状态标志;只读. 一个字节的控制寄存器:7 个控制标志;读写. 其中前三个寄存器(输入,输出,状态)可以通过$60 和$64 端口直接存取,读写$60 和$64 端口所实 现的功能如下:
端口 $60 $60 $64 $64
读写 读 写 读 写
功能 读输入缓冲区 写输出缓冲区 读状态寄存器 发送命令
写$64 端口不会写入到任何特定的寄存器中,但是解释为发送命令给 i8042.如果命令接收一个参数, 则参数被发往$60 端口.同样,命令的任何返回结构可以从$60 端口读出.i8042 的状态标志是从$64 端口 读出的.它们包含了错误信息,状态信息和输入输出缓冲区里有无数据的指示.这些标志的定义如下: Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 │SYS │IBF │OBF │
┌——┬——┬——┬——┬——┬——┬——┬——┐ │PERR│TO │MOBF│INH │A2 └——┴——┴——┴——┴——┴——┴——┴——┘ 其中标志位 OBF 最重要(其它标志位的意思请参考 PS/2 协议),它表示输出缓冲区是否已满,是否可 以写入输出缓冲区.0 表示输出缓冲区空,1 表示输出缓冲区已满.所以在向$60 端口写入数据之前要检查 该标志位是否已被置 0.另外在向$64 端口发送命令之前也要检查该标志位,已确保上次的操作已经完成. 向指定端口写入数据的程序如下,其中使用了内嵌汇编对端口进行操作.注意:鼠标键盘是慢速设备,每 次操作时要有一定的延时: procedure SetByte(Por,Cod : Byte); begin Sleep(1); asm PUSH EAX PUSH EDX //等待状态寄存器标志位 OBF 置 0 @Loop: IN AL,$64 AND AL,01b JNZ @Loop //写入数据 MOV AL,Cod MOV DL,Por MOV DH,0 OUT DX,AL POP EDX POP EAX end; end; 发送命令给 i8042 就是写$64 端口.在命令发送后,命令参数写到$60 端口.命令中用来模拟鼠标键盘 操作的有两条(其它命令请参考 PS/2 协议):
$D2:写键盘缓冲区,把参数写到输入缓冲区就像从键盘接收到的一样. $D3:写鼠标缓冲区,把参数写到输入缓冲区就像从鼠标接收到的一样. 例如:按下"A"键,利用上面的程序可以写成"SetByte($64,$D2); SetByte($60,$1E);".注意: 如果向$60 端口发送的数据不只 1 个字节,那么发送的每个字节前都要发送一条命令,例如:按下"Inser t"键的程序为"SetByte($64,$D2); SetByte($60,$E0); SetByte($64,$D2); SetByte($60,$52);",要 调用 SetByte 四次.知道了如何向 i8042 发送命令,下面