技术开发 频道

.Net下的鼠标键盘模拟技术实战


简介

    本文讲述如何利用.Net框架来实现对系统的鼠标键盘模拟。鼠标键盘的模拟技术在游戏的外挂中使用比较多,但更多的时候它用于在软件UI自动化测试。本文不探讨该技术的应用领域主要探讨在.Net框架下面实现鼠标键盘模拟的技术选择。很多人在想到鼠标键盘模拟技术的时候第一反映是驱动开发,其实实现一个驱动来完成鼠标键盘的模拟只是其中的一种方式。我将鼠标键盘的模拟场景分为三种情况:  基于同一进程中的鼠标键盘模拟 、基于不同进程中的鼠标键盘模拟 、基于驱动方式的鼠标键盘模拟

1.基于同一进程中的鼠标键盘模拟

    我们先了解一下一种最简单的方式:基于同一进程中的鼠标键盘模拟。这种方式比较简单的原因是所有的控件都在自己的程序控制范围中,只要通过调用控件对应的事件函数就可以实现。
例:
private void buttonParent_Click(object sender, System.EventArgs e) 
{
// 调用buttonObject的Click事件
buttonObject.PerformClick();
};
   在这节我们主要探讨在不同进程中的鼠标键盘模拟技术,我将这块分成两个部分:
   第一,针对一般引用程序的鼠标键盘模拟主要用到了几个系统的API:
   鼠标事件API
   [System.Runtime.InteropServices.DllImport("user32")]
   private static extern int mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
   dwFlags : 指示鼠标的行为和点击标志
   dx, dy : 指示鼠标的绝对坐标位置偏移
   dwData : 主要用在滚轮中,给出滚轮的滚动偏移
   dwExtraInfo : 指示要传递给鼠标的额外数据

   键盘事件API
   [System.Runtime.InteropServices.DllImport("user32")]
   private static extern void keybd_event(int bVk, int bScan, int dwFlags, int dwExtraInfo);
   bVk : 指示按键的虚拟键值
   bScan : 指示按键的扫描码,不过这个字段已经不用了
   dwFlags : 指示按键的操作标志 KEYEVENTF_EXTENDEDKEY , KEYEVENTF_KEYUP
   dwExtraInfo : 指示要传递给按键的额外数据
   注:
   推荐一个关于.Net环境中调用系统API的查询网站www.pinvoke.net


   下面这个例子是如何在程序中使用以上的API函数的片段:
// 定义鼠标事件 
private enum MouseEvent : uint
{
Move = 0x0001,
LeftDown = 0x0002,
LeftUp = 0x0004,
RightDown = 0x0008,
RightUp = 0x0010,
MiddleDown = 0x0020,
MiddleUp = 0x0040,
XDown = 0x0080,
XUp = 0x0100,
Wheel = 0x0800,
VirtualDesk = 0x4000,
Absolute = 0x8000
}

// 处理Click事件
public void Click(ButtonType type)
{
switch (type)
{
case ButtonType.Left:
mouse_event(MouseEvent.LeftDown, 0, 0, 0, UIntPtr.Zero);
mouse_event(MouseEvent.LeftUp, 0, 0, 0, UIntPtr.Zero);
break;

case ButtonType.Middle:
mouse_event(MouseEvent.MiddleDown, 0, 0, 0, UIntPtr.Zero);
mouse_event(MouseEvent.MiddleUp, 0, 0, 0, UIntPtr.Zero);
break;

case ButtonType.Right:
mouse_event(MouseEvent.RightDown, 0, 0, 0, UIntPtr.Zero);
mouse_event(MouseEvent.RightUp, 0, 0, 0, UIntPtr.Zero);
break;
}
}
    通过这两个API函数的调用就可以实现对鼠标和键盘的模拟,当然仅仅知道这两个函数是不够的,因为我们想将鼠标和键盘的消息发送给其他程序,还需要先捕获该程序的内存句柄。这个时候就需要用到System.Diagnostics.Process类来处理,这个类可以根据应用程序的名称来捕获程序.
using System.Diagnostics; 
Process[] pros = Process.GetProcessesByName(appName);
   当然,我们还可以设置全局的鼠标和键盘钩子来截获鼠标和键盘数据处理再转发,这种方法是按键木马程序常用的。
   第二,为什么我们要单独提出来针对游戏的鼠标键盘模拟呢?主要的原因就是大多数的游戏是使用DirectX开发的,而DirectX的HAL层事件是不通过系统消息队列的来完成鼠标和键盘操作的,而是直接与驱动程序交互。那么上面我们讲的那种方法就没有办法应用到游戏中来。这里就需要用到WinIo.dll这个库文件来帮助我们完成这样一个任务。WinIo.dll是网上的一个免费的库文件,它在实现机制上绕过了windows的很多底层限制。因此,我们可以利用这个dll文件来轻松的完成鼠标键盘模拟,在这里要强调一下这个dll文件只针对ps端子接口的鼠标键盘有效,对USB接口的鼠标键盘是无效的。如果想实现USB接口的鼠标键盘模拟就需要进入到第三节基于驱动方式的鼠标键盘模拟。以下是在开发中需要用到主要几个WinIo.dll的API接口:
[DllImport("winio.dll", EntryPoint = "InitializeWinIo")]
private static extern bool InitializeWinIo();
[DllImport("winio.dll", EntryPoint = "GetPortVal")]
private static extern bool GetPortVal(int wPortAddr, out Int32 pdwPortVal, byte bSize);
[DllImport("winio.dll", EntryPoint = "SetPortVal")]
private static extern bool SetPortVal(int wPortAddr, Int32 dwPortVal, byte bSize);
[DllImport("winio.dll", EntryPoint = "ShutdownWinIo")]
private static extern bool ShutdownWinIo();
   注:
   WinIo.dll可以通过www.google.com搜索引擎查找下载。

3.基于驱动方式的鼠标键盘模拟

    在C#中实现基于驱动的鼠标键盘模拟。现在的大多数鼠标和键盘都采用了USB接口,那么我们想更深层次的模拟鼠标和键盘的操作就需要对USB端口进行读写。那么最好的方法是自己写一个鼠标或键盘驱动,不过这种方案对于只是想模拟鼠标和键盘事件的开发者来说是不可取的,主要是因为没有经过充分测试的驱动会影响系统的稳定行,并且要开发一个完整的驱动是需要比较长的周期的。幸好,Microsoft还是为我们提供了几个操作USB接口的API函数,下面我来说明一下我们将在程序中用到的这几个API函数:

(1) 获取一个可用的GUID函数,因为每一个设备驱动都需要一个唯一标识那就是GUID。
[DllImport("hid.dll", SetLastError = true)]
protected static extern void HidD_GetHidGuid(out Guid gHid);

(2) 为设备的详细信息分配一个内存块函数
[DllImport("setupapi.dll", SetLastError = true)]
protected static extern IntPtr SetupDiGetClassDevs(ref Guid gClass, [MarshalAs(UnmanagedType.LPStr)] string strEnumerator, IntPtr hParent, uint nFlags);

(3) 释放上一个函数分配的内存函数
[DllImport("setupapi.dll", SetLastError = true)]
protected static extern int SetupDiDestroyDeviceInfoList(IntPtr lpInfoSet);

(4) 从设备信息中获取设备的接口数据
[DllImport("setupapi.dll", SetLastError = true)]
protected static extern bool SetupDiEnumDeviceInterfaces(IntPtr lpDeviceInfoSet, uint nDeviceInfoData, ref Guid gClass, uint nIndex, ref DeviceInterfaceData oInterfaceData);

(5) 从设备接口数据中获取接口的详细信息的函数
[DllImport("setupapi.dll", SetLastError = true)]
protected static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr lpDeviceInfoSet, ref DeviceInterfaceData oInterfaceData, IntPtr lpDeviceInterfaceDetailData, uint nDeviceInterfaceDetailDataSize, ref uint nRequiredSize, IntPtr lpDeviceInfoData);

(6) 为设备信息的删除和插入注册一个窗口
[DllImport("user32.dll", SetLastError = true)]
protected static extern IntPtr RegisterDeviceNotification(IntPtr hwnd, DeviceBroadcastInterface oInterface, uint nFlags);

(7) 释放上一个函数创建的窗口资源
[DllImport("user32.dll", SetLastError = true)]
protected static extern bool UnregisterDeviceNotification(IntPtr hHandle);

(8) 获取一个打开的设备的详细信息到内存
[DllImport("hid.dll", SetLastError = true)]
protected static extern bool HidD_GetPreparsedData(IntPtr hFile, out IntPtr lpData);

(9) 释放上一个函数所处理的内存
[DllImport("hid.dll", SetLastError = true)]
protected static extern bool HidD_FreePreparsedData(ref IntPtr pData);

(10) 从HidD_GetPreparsedData函数处理的内存中获取设备的可用信息
[DllImport("hid.dll", SetLastError = true)]
protected static extern int HidP_GetCaps(IntPtr lpData, out HidCaps oCaps);

(11) 创建我们要操作驱动的映射文件
[DllImport("kernel32.dll", SetLastError = true)]
protected static extern IntPtr CreateFile([MarshalAs(UnmanagedType.LPStr)] string strName, uint nAccess, uint nShareMode, IntPtr lpSecurity, uint nCreationFlags, uint nAttributes, IntPtr lpTemplate);

(12) 关闭设备的文件句柄
[DllImport("kernel32.dll", SetLastError = true)]
protected static extern int CloseHandle(IntPtr hFile);

   在这里大家可能会被需要调用如此多的API接口函数所吓到。是的相对于第一,二节来说这节用到了比较多的API接口,不过稍微耐心的看一下这些API其实很多都是成对出现的,参数也差不多并没有看起来这么的吓人。下面我说明一下如何去使用这些API接口。首先,我们需要知道鼠标和键盘的USB设备在windows的系统上都被称为人体学接口设备,并且每个设备都有唯一性两个编号VID和PID,可在驱动设备的详细信息中可以看到。所以我们可以通过这两个编号来查找指定的设备驱动。
public static HIDDevice FindDevice(int nVid, int nPid, Type oType) 
{
string strPath = string.Empty;
// 创建查找字符串
string strSearch = string.Format("vid_{0:x4}&pid_{1:x4}", nVid, nPid);
Guid gHid = HIDGuid;

// 获取所有HID设备信息的列表
IntPtr hInfoSet = SetupDiGetClassDevs(ref gHid, null, IntPtr.Zero,
DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
try
{
// 分配一个设备信息接口数据块
DeviceInterfaceData oInterface = new DeviceInterfaceData();
oInterface.Size = Marshal.SizeOf(oInterface);

// 查找每个已连接到系统的设备
int nIndex = 0;
while (SetupDiEnumDeviceInterfaces(hInfoSet, 0, ref gHid, (uint)nIndex, ref oInterface))
{
// 获取设备的路径
string strDevicePath = GetDevicePath(hInfoSet, ref oInterface);
if (strDevicePath.IndexOf(strSearch) >= 0)
{
// 创建一个设备对象
HIDDevice oNewDevice = (HIDDevice)Activator.CreateInstance(oType);
// 使用路径来初始化
oNewDevice.Initialise(strDevicePath);
return oNewDevice;
}
nIndex++;
}
}
catch (Exception ex)
{
throw HIDDeviceException.GenerateError(ex.ToString());
}
finally
{
// 释放所有的设备信息资源
SetupDiDestroyDeviceInfoList(hInfoSet);
}
return null;
}
总结:

   本文主要列举了鼠标和键盘模拟的一些技术,提供了如何在.Net框架下使用windows的原生API接口和在什么样的需求下使用什么样的技术来实现鼠标和键盘的模拟功能。本文没有列举所有的源代码,只希望这篇文章能起到一个抛砖引玉的作用,使大家对鼠标和键盘模拟开发技术有一个整体了解!
0
相关文章