鼠标键盘模拟 【转】


鼠标键盘模拟 【转】
2011年06月07日
  指定hwnd后可以后台发送,这是相比后面2种方式的优势之一。
  例如:
  以当前程序的某个输入框为例:
  HWND tw2 = GetDlgItem(IDC_EDIT1)->m_hWnd;
  ::SendMessage(tw2,WM_CHAR,s[i],0);
  单击按钮:
  HWND tw3 = GetDlgItem(IDOK)->m_hWnd;
  ::SendMessage(tw3,WM_LBUTTONDOWN,0,0);
  ::SendMessage(tw3,WM_LBUTTONUP,0,0);
  以记事本为例
  wnd = ::FindWindow("notepad", NULL); 
  wnd = FindWindowEx(wnd,0,"Edit",NULL); 
  ::SendMessage(tw2,WM_CHAR,s[i],0);
  有人就说了,我不知道名字叫Edit啊,有下面的方法:
  CWnd * pwnd = FindWindow("notepad", NULL);
  CWnd * p2 = pwnd->GetTopWindow();
  wnd = p2->m_hWnd;
  ::SendMessage(tw2,WM_CHAR,s[i],0);
  又有人说了,如果连notepad也不知道呢...
  我说,进程ID总知道了吧...遍历进程池得到想要的进程ID,然后找到指定ID的hwnd
  struct EnumParam 
  { 
  HWND hMainWnd; 
  DWORD dwProcessID; 
  }; 
  BOOL CALLBACK EnumWinProc(HWND hwnd, LPARAM lParam) 
  { 
  DWORD dwID; 
  EnumParam* pep = (EnumParam*)lParam; 
  GetWindowThreadProcessId(hwnd,&dwID); 
  if (dwID == pep->dwProcessID) { 
  pep->hMainWnd = hwnd; 
  return 0; 
  } 
  return TRUE; 
  } 
  EnumParam ep; 
  STARTUPINFO si; 
  PROCESS_INFORMATION pi; 
  ep.hMainWnd = NULL; 
  memset(&si, 0, sizeof(si)); 
  si.cb = sizeof(STARTUPINFO); 
  if (CreateProcess(NULL,"notepad.exe c:.txt", 
  NULL,NULL,FALSE,0,NULL,NULL,&si,&pi)) 
  { 
  CloseHandle(pi.hThread); 
  CloseHandle(pi.hProcess); 
  ep.dwProcessID = pi.dwProcessId; 
  while(!ep.hMainWnd) { 
  EnumWindows((WNDENUMPROC)EnumWinProc, (long)&ep); 
  //没有下面的一行,CPU的使用率会高居不下,同时其它的程序
  //也会执行的很慢(包括程序启动的Notepad.exe)
  if (ep.hMainWnd==NULL) Sleep(20); 
  } 
  //EnumWindows((WNDENUMPROC)EnumWinProc, (long)&ep); 
  } 
  //此时的 ep.hMainWnd 就是你要的NodePad.exe窗口的句柄
  //接下来由hwnd得到CWND *
  wnd = ep.hMainWnd;
  if(wnd == NULL) AfxMessageBox("Fasle");
  CWnd * tc = FromHandle(wnd); //获取  wnd = tc->GetTopWindow()->m_hWnd; ::SendMessage(tw2,WM_CHAR,s[i],0); //OK啦 用VC模拟按键我最先想到的当然就是sendmessage,这个函数用消息来给某个窗口发送就可以了:SendMessage(WM_KEYDOWN, ‘a’, 0L); 
  这是通过局部键盘消息来模拟按键。这个方法有一个极大的好处,就是:它可以实现后台按键,也就是说他对你的前台操作不会有什么影响。比如,你可以用这个方法做个程序在游戏中模拟按键来不断地执行某些重复的操作,而你则一边喝茶一边与QQ上的MM们聊得火热,它丝毫不会影响你的前台操作。无论目标程序是否获得焦点都没有影响,这就是后台模拟按键的原理。 全局级模键盘鼠标模拟函数,不能指定窗口。
  相对(1)的发送消息而言,(1)在模拟普通按键的时候确实是够用了,但是在模拟功能按键的时候,比如num lock 和 print screen等按键的时候就会出现问题,模拟不成功。
  这个时候我们就需要用到keybd_event这个函数,这个函数就不是用发送消息来模拟键盘了,而直接是模拟键盘实践
  使用案例
  keybd_event( VK_SNAPSHOT,0x45,KEYEVENTF_EXTENDEDKEY | 0, 0 );
  用于模拟print screen 按键,从而从根本上解决了那些特殊功能的按键不能用sendmessage或postmessage模拟的问题
  有些程序(特别是一些游戏)出于某些原因,会禁止用户对它使用模拟按键程序,这个怎么实现呢?比如可以在程序中检查一下,如果发现自己不是活动窗口,就不接受键盘消息。或者仔细检查一下收到的键盘消息,你会发现真实的按键和模拟的按键消息总是有一些小差别,从这些小差别上,目标程序就能判断出:这是假的!是伪造的!!因此,如果用PostMessage发送局部消息模拟按键不成功的话,你可以试一试全局级的键盘消息,看看能不能骗过目标程序。 效果和(2)差不多,但据说,MSDN上推荐用SendInput。
  例1:
  MOUSEINPUT mi;
  mi.dx = 100;
  mi.dy = 100;
  mi.mouseData = 0;
  mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
  mi.dwExtraInfo = 0;
  mi.time = 0;
  MOUSEINPUT mi2;
  mi2.dx = 200;
  mi2.dy = 200;
  mi2.mouseData = 0;
  mi2.dwFlags = MOUSEEVENTF_LEFTDOWN;
  mi2.dwExtraInfo = 0;
  mi2.time = 0;
  INPUT input1;
  input1.type = INPUT_MOUSE;
  input1.mi = mi;
  INPUT input2;
  input2.type = INPUT_MOUSE;
  input2.mi = mi2;
  //输入
  INPUT input[2];
  input[0] = input1;
  input[1] = input2;
  int size = sizeof(INPUT);
  SendInput(2, input, size); 如果上面的方法你都试过了,可是你发现目标程序却仍然顽固的不接受你模拟的消息,寒~~~~~~~~~还好,我还剩下最后一招,这就是驱动级模拟:直接读写键盘的硬件端口!
  有一些使用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
  

猜你喜欢

转载自nbq440kx.iteye.com/blog/1361683