注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

yeye55的博客

编程就象品一杯清茶,清淡却又深厚。

 
 
 

日志

 
 
关于我

宅男一只,喜欢编程。没事的时候就喜欢抱着电脑看看电影、看看动漫、听听音乐、打打网游、逛逛论坛。清静的时候喜欢专研编程技术,但是发现自己至今仍然只是一只三脚猫。

网易考拉推荐

Delphi下利用SendInput模拟鼠标键盘  

2011-11-04 13:41:52|  分类: 辅助程序设计 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

  本文最早于2009年1月13日在编程论坛(programbbs.com)上发表,页面地址:http://programbbs.com/bbs/view12-17219-1.htm 。

1 前言

  发了一篇《Delphi下利用WinIo模拟鼠标键盘详解》,再发一个利用SendInput模拟键盘鼠标的程序,以作对比。

2 SendInput

  SendInput可以将指定的鼠标键盘消息插入到系统消息队列,从而实现对鼠标键盘的模拟。有很多程序对SendInput进行了屏蔽,但不是所有的。所以这里介绍一下SendInput的使用。我已经将主要的模拟功能写在了一个单元文件中:SIMouseKeyboard.pas,调用该单元文件中的相关函数就可以实现鼠标键盘的模拟。该单元文件的下载见本楼末尾。SendInput的参数其实很简单,在Windows.pas就有函数的声明如下:

function SendInput(cInputs: UINT; var  pInputs: TInput; cbSize: Integer): UINT; stdcall;

  cInputs:定义pInputs中记录数组的元素数目。pInputs:TInput类型记录数组的第1个元素。每个元素代表插人到系统消息队列的键盘或鼠标事件。cbSize:定义TInput的大小,一般为SizeOf(TInput)。函数返回成功插入系统消息队列中事件的数目,失败返回0。调用SendInput关键的就是要搞清楚它的几个记录结构的意思,在Windows.pas中对TInput的声明如下:

tagINPUT = packed record
  Itype: DWORD;
  case Integer of
    0: (mi: TMouseInput);
    1: (ki: TKeybdInput);
    2: (hi:  THardwareInput);
end;
TInput = tagINPUT;

  其中mi、ki、hi是3个共用型的记录结构,Itype指出记录结构中所使用的类型,它有3个值。INPUT_MOUSE:表示使用mi记录结构,忽略ki和hi;INPUT_KEYBOARD:表示使用ki记录结构,忽略mi和hi。

3 键盘模拟

  TKeybdInput记录结构的声明如下:

tagKEYBDINPUT = packed record
  wVk: WORD;
  wScan: WORD;
  dwFlags: DWORD;
  time: DWORD;
  dwExtraInfo: DWORD;
end;
TKeybdInput = tagKEYBDINPUT;

  其中wVk是将要操作的按键的虚键码。wScan是安全码,一般不用。dwFlags指定键盘所进行的操作,为0时表示按下某键,KEYEVENTF_KEYUP表示放开某键。time是时间戳,可以使用API函数GetTickCount的返回值。dwExtraInfo是扩展信息,可以使用API函数GetMessageExtraInfo的返回值。例如击键“A”的程序如下:

procedure KeyPressA;
var
    Inputs : array [0..1]  of TInput;
begin
     Inputs[0].Itype:=INPUT_KEYBOARD;
    with Inputs[0].ki do
    begin
         wVk:=VK_A;
         wScan:=0;
         dwFlags:=0;
         time:=GetTickCount;
         dwExtraInfo:=GetMessageExtraInfo;
    end;
     Inputs[1].Itype:=INPUT_KEYBOARD;
    with Inputs[1].ki do
    begin
         wVk:=VK_A;
         wScan:=0;
         dwFlags:=KEYEVENTF_KEYUP;
         time:=GetTickCount;
         dwExtraInfo:=GetMessageExtraInfo;
    end;
     SendInput(2,Inputs[0],SizeOf(TInput));
end;

  注意:在Windows.pas单元中并没有字母和数字的虚键码的声明,在我写的SIMouseKeyboard.pas单元文件中对所有的虚键码进行了重新声明,包含了字母、数字和标点符号。

4 鼠标模拟

  TMouseInput记录结构的声明如下:

tagMOUSEINPUT = packed record
  dx: Longint;
  dy: Longint;
  mouseData: DWORD;
  dwFlags: DWORD;
  time: DWORD;
  dwExtraInfo: DWORD;
end;
TMouseInput = tagMOUSEINPUT;

  其中dx、dy是鼠标移动时的坐标差(不是象素单位),在鼠标移动时有效。mouseData是鼠标滚轮滚动值,在滚动鼠标滚轮时有效。当mouseData小于0时向下滚动,当mouseData大于0时向上滚动,mouseData的绝对值一般设为120。dwFlags指定鼠标所进行的操作,例如,MOUSEEVENTF_MOVE表示移动鼠标,MOUSEEVENTF_LEFTDOWN表示按下鼠标左键,MOUSEEVENTF_LEFTUP表示放开鼠标左键。time是时间戳,可以使用API函数GetTickCount的返回值。dwExtraInfo是扩展信息,可以使用API函数GetMessageExtraInfo的返回值。例如单击鼠标左键的程序如下:

procedure MouseClick;
var
    Inputs : array [0..1]  of TInput;
begin
     Inputs[0].Itype:=INPUT_MOUSE;
    with Inputs[0].mi do
    begin
         dx:=0;
         dy:=0;
         mouseData:=0;
         dwFlags:=MOUSEEVENTF_LEFTDOWN;
         time:=GetTickCount;
         dwExtraInfo:=GetMessageExtraInfo;
    end;
     Inputs[1].Itype:=INPUT_MOUSE;
    with Inputs[1].mi do
    begin
        dx:=0;
         dy:=0;
         mouseData:=0;
         dwFlags:=MOUSEEVENTF_LEFTUP;
         time:=GetTickCount;
         dwExtraInfo:=GetMessageExtraInfo;
    end;
     SendInput(2,Inputs[0],SizeOf(TInput));
end;

  鼠标的移动总是很麻烦,上面的dx、dy不是以象素为单位的,而是以鼠标设备移动量为单位的,它们之间的比值受鼠标移动速度设置的影响。具体的解决方法我已经在《Delphi下利用WinIo模拟鼠标键盘详解》一文中进行了讨论,这里不再重复。dwFlags可以设置一个MOUSEEVENTF_ABSOLUTE标志,这使得可以用另外一种方法移动鼠标。当dwFlags设置了MOUSEEVENTF_ABSOLUTE标志,dx、dy为屏幕坐标值,表示将鼠标移动到dx,dy的位置。但是这个坐标值也不是以象素为单位的。这个值的范围是0到65535($FFFF),当dx等于0、dy等于0时表示屏幕的最左上角,当dx等于65535、dy等于65535时表示屏幕的最右下角,相当于将屏幕的宽和高分别65536等分。API函数GetSystemMetrics(SM_CXSCREEN)可以返回屏幕的宽度,函数GetSystemMetrics(SM_CYSCREEN)可以返回屏幕的高度,利用屏幕的宽度和高度就可以将象素坐标换算成相应的dx、dy。注意:这种换算最多会出现1象素的误差。例如:将鼠标指针移动到屏幕150,120坐标处的程序如下:

procedure MouseMove;
var
    Input : TInput;
begin
     Input.Itype:=INPUT_MOUSE;
    with Input.mi do
    begin
         dx:=($FFFF div (GetSystemMetrics(SM_CXSCREEN)-1)) * 150;
         dy:=($FFFF div (GetSystemMetrics(SM_CYSCREEN)-1)) * 120;
        mouseData:=0;
         dwFlags:=MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE;
         time:=GetTickCount;
         dwExtraInfo:=GetMessageExtraInfo;
    end;
     SendInput(1,Input,SizeOf(TInput));
end;

5 SendInput与WInIo的对比

  在《Delphi下利用WinIo模拟鼠标键盘详解》一文中我已经说了WinIo的很多缺点,SendInput几乎没有这些缺点。SendInput的模拟要比WinIo简单的多。事件是被直接插入到系统消息队列的,所以它的速度比WinIo要快。系统也会保证数据的完整性,不会出现数据包混乱的情况。利用“绝对移动”可以将鼠标指针移动到准确的位置,同鼠标的配置隔离不会出现兼容性的问题。SendInput的缺点也是最要命的,它会被一些程序屏蔽。所以说在SendInput与WInIo都可以使用的情况下优先考虑SendInput。另外SendInput与WInIo可以接合使用,一些程序对鼠标左键单击敏感,可以使用WinIo模拟鼠标左键单击,其它操作由SendInput模拟。

6 SIMouseKeyboard.pas的使用

  我在SIMouseKeyboard.pas单元文件中对所有的虚键码进行了重新声明,包含了Windows.pas单元中没有声明的字母、数字和标点符号。在SIMouseKeyboard.pas单元文件中共有9个函数,使用方法如下:

  1、procedure SIKeyDown(Key :WORD);

  按下指定的键。Key为虚键码。

  2、procedure SIKeyUp(Key : WORD);

  放开指定的键。Key为虚键码。

  3、procedure SIKeyPress(Key :WORD; Interval : Cardinal);

  按下并放开指定的键,Interval为按下和放开之间的时间间隔。注意:本函数不支持重复机打,即无论Interval设的多么大都只有一次按键。

  4、procedure SIKeyInput(const Text: String; Interval : Cardinal);

  模拟键盘输入指定的文本,返回是否成功。文本中只能是单字节字符(#32~#126)、Tab(#9)键和回车键(#13),即可以从键盘上输入的字符,不能是汉字,其它字符会被忽略。Interval为按下和放开键之间的时间间隔,单位毫秒。

  示范程序,组合键Ctrl+A如下:

SIKeyDown(VK_CONTROL); //按下Ctrl
SIKeyPress(VK_A);       //击键A
SIKeyUp(VK_CONTROL);   //放开Ctrl

  5、procedure SIMouseDown(Key :WORD);

  按下鼠标的指定键。Key为虚键码,鼠标左键为VK_LBUTTON,右键为VK_RBUTTON,中键为VK_MBUTTON。

  6、procedure SIMouseUp(Key :WORD);

  放开鼠标的指定键。Key为虚键码,鼠标左键为VK_LBUTTON,右键为VK_RBUTTON,中键为VK_MBUTTON。

  7、procedure SIMouseClick(Key :WORD; Interval : Cardinal);

  单击鼠标的指定键,Interval为按下和放开之间的时间间隔。过快的单击可能会使一些程序无法识别,适当的调整Interval的值可以解决这个问题。

  8、procedure SIMouseWheel(dZ :Integer);

  滚动鼠标的滚轮。当dZ小于0时向下滚动,当dZ大于0时向上滚动,dZ的绝对值一般设为120。

  9、procedure SIMouseMoveTo(X,Y :Integer; MaxMove : Integer; Interval : Cardinal);

  将鼠标指针移动到指定位置,返回是否成功。X和Y为象素值,X和Y的值的范围不能超出屏幕,MaxMove为移动时的dX和dY的最大值,Interval为两次移动之间的时间间隔,一些程序对鼠标移动速度敏感,当鼠标移动太快时无法对鼠标做出反应,适当的设置MaxMove和Interval的值可以解决这个问题。

  示范程序,拖放到指定位置如下:

SIMouseDown(VK_LBUTTON); //按下鼠标左键
SIMouseMoveTo(780,300);  //移动到指定位置
SIMouseUp(VK_LBUTTON);   //放开鼠标左键

更新于2010年1月31日

  更新SIMouseKeyboard.pas文件,一个奇怪的BUG。SIMouseKeyboard.pas主要利用API函数SendInput模拟键盘鼠标。SendInput允许一次性多个输入,例如下面的代码:

//模拟输入“a”
procedure KeyPressA;
var
    Inputs : array [0..1]  of TInput;
begin
     Inputs[0].Itype:=INPUT_KEYBOARD;
    with Inputs[0].ki do
    begin
         wVk:=VK_A;
         wScan:=0;
         dwFlags:=0;
         time:=GetTickCount;
         dwExtraInfo:=GetMessageExtraInfo;
    end;
    //
     Inputs[1].Itype:=INPUT_KEYBOARD;
    with Inputs[1].ki do
    begin
         wVk:=VK_A;
         wScan:=0;
         dwFlags:=KEYEVENTF_KEYUP;
         time:=GetTickCount;
         dwExtraInfo:=GetMessageExtraInfo;
    end;
     SendInput(2,Inputs[0],SizeOf(TInput));
end;

  一共是两个输入,按下键和放开键。但是最近我给系统(Win2000)打了几个补丁,试用了几款输入法程序后,发现SendInput居然变成不支持多个输入!第一个参数必需是1,否则模拟输入没有反应。以前的测试明明是很正常的,我也不知道问题出在那里?上面的程序必需修改如下:

//模拟输入“a”
procedure KeyPressA;
var
    Input : TInput;
begin
     Input.Itype:=INPUT_KEYBOARD;
    with Input.ki do
    begin
         wVk:=VK_A;
         wScan:=0;
         dwFlags:=0;
         time:=GetTickCount;
         dwExtraInfo:=GetMessageExtraInfo;
    end;
     SendInput(1,Input,SizeOf(TInput));
    //
     Input.Itype:=INPUT_KEYBOARD;
    with Input.ki do
    begin
         wVk:=VK_A;
         wScan:=0;
         dwFlags:=KEYEVENTF_KEYUP;
         time:=GetTickCount;
         dwExtraInfo:=GetMessageExtraInfo;
    end;
     SendInput(1,Input,SizeOf(TInput));
end;

  在SIMouseKeyboard.pas文件中受到影响的函数是SIKeyInput,我已经修改了该函数并测试成功。利用下面的链接可以下载更新后的SIMouseKeyboard.pas文件。

相关文件下载

  SIMouseKeyboard.pas(已更新)单元文件下载(点这里

  评论这张
 
阅读(1509)| 评论(1)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017