今天大年初二,相信大家听说过WINDOWS系统下的按键精灵一类的软件,会录制一段人类操作鼠标的操作,然后自动重复,减轻人类重复工作的工作量,这也算是很古早时代的算是自动化操作的一种方式吧。
这次采用C#的鼠标钩子来实现这个方案,之所以叫鼠标钩子是因为早期一批人翻译这个HOOK就翻译成钩子,现在的人听的云里雾里,其实你可以理解为就像你可以使用现实生活中的钩子来捕获或挂住某些物体一样,编程中的钩子可以帮助你"捕获"或"挂住"某些事件或函数。
举一个简单的例子,假设你在一个大型购物中心,你想知道有多少人进入了这个购物中心。为此,你可以在入口处放一个人,每当有人进入购物中心时,这个人就用记事本记下一笔。这个人就像是一个"钩子",他"捕获"了进入购物中心的事件,并执行了一些操作(即记笔记)。那既然有鼠标钩子就肯定也有其它钩子,例如键盘钩子、WINDOWS消息钩子等等,像键盘钩子就很好理解,就是记录键盘的输入信息,更早期的时候,用这个方法是可以捕获到类似游戏登录账号密码、QQ账号密码一类的,现在这些输入文本框都升级了,在2003年以前都是获取到的。好了,今天我们讲的是鼠标钩子,回归主题。
那要做一个类似按键精灵类软件,那么首先得采集到鼠标的相应事件,如左键单击,右键单击、鼠标移动等等,还有按键移动等事件的时间间隔顺序也需要采集到,否则没有时间间隔的操作一来不像真人操作,二来很多事情是需要一定延时才能正常的操作,例如有些软件双击后需要1,2秒才能打开,就得延时这1,2秒。好了,废话不多说,直接开干。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.WINDOWS.Forms;
public class RecordForm : Form
{
private delegate IntPtr LowLevelMouseProc(Int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern uint SendInput(uint nINPUTs, INPUT[] pInputs, int cbSize);
private const int WH_MOUSE_LL = 14;
private LowLevelMouseProc _proc;
private IntPtr _hookID = IntPtr.Zero;
private List<MouseEvent> mouseEvents = new List<MouseEvent>();
private Stopwatch stopwatch = new Stopwatch();
public RecordForm()
{
_proc = HookCallback;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_hookID = SetHook(_proc);
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
UnhookWindowsHookEx(_hookID);
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Right)
{
stopwatch.Reset();
mouseEvents.Clear();
stopwatch.Start();
}
else if (e.Button == MouseButtons.Left)
{
stopwatch.Stop();
Playback();
}
}
private IntPtr SetHook(LowLevelMouseProc proc)
{
using (var curProcess = System.Diagnostics.Process.GetCurrentProcess())
using (var curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_MOUSE_LL, proc, Marshal.GetHINSTANCE(curModule.ModuleName), 0);
}
}
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && stopwatch.IsRunning)
{
MSLLHOOKstruct hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
mouseEvents.Add(new MouseEvent(hookStruct.pt, (MouseMessages)wParam, stopwatch.ElapsedMilliseconds));
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
private void Playback()
{
long previousTime = 0;
foreach (var mouseEvent in mouseEvents)
{
Thread.Sleep((int)(mouseEvent.Time - previousTime));
previousTime = mouseEvent.Time;
INPUT input = new INPUT();
input.type = INPUT_MOUSE;
input.U.mi.dx = mouseEvent.Location.X * (65536 / Screen.PrimaryScreen.Bounds.Width); // x being coord in pixels
input.U.mi.dy = mouseEvent.Location.Y * (65536 / Screen.PrimaryScreen.Bounds.Height); // y being coord in pixels
input.U.mi.mouseData = 0;
switch (mouseEvent.Message)
{
case MouseMessages.WM_LBUTTONDOWN:
input.U.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
break;
case MouseMessages.WM_LBUTTONUP:
input.U.mi.dwFlags = MOUSEEVENTF_LEFTUP;
break;
case MouseMessages.WM_MOUSEMOVE:
input.U.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
break;
case MouseMessages.WM_MOUSEWHEEL:
input.U.mi.dwFlags = MOUSEEVENTF_WHEEL;
break;
case MouseMessages.WM_RBUTTONDOWN:
input.U.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;
break;
case MouseMessages.WM_RBUTTONUP:
input.U.mi.dwFlags = MOUSEEVENTF_RIGHTUP;
break;
}
SendInput(1, new INPUT[] { input }, Marshal.SizeOf(typeof(INPUT)));
}
}
private const int INPUT_MOUSE = 0;
private const uint MOUSEEVENTF_MOVE = 0x0001;
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
private const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
private const uint MOUSEEVENTF_RIGHTUP = 0x0010;
private const uint MOUSEEVENTF_ABSOLUTE = 0x8000;
private const uint MOUSEEVENTF_WHEEL = 0x0800;
private struct INPUT
{
public int type;
public InputUnion U;
}
[StructLayout(LayoutKind.Explicit)]
private struct InputUnion
{
[FieldOffset(0)]
public MOUSEINPUT mi;
}
private struct POINT
{
public int x;
public int y;
}
private struct MOUSEINPUT
{
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
private struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
private enum MouseMessages
{
WM_LBUTTONDOWN = 0x0201,
WM_LBUTTONUP = 0x0202,
WM_MOUSEMOVE = 0x0200,
WM_MOUSEWHEEL = 0x020A,
WM_RBUTTONDOWN = 0x0204,
WM_RBUTTONUP = 0x0205,
WM_LBUTTONDBLCLK = 0x0203,
WM_RBUTTONDBLCLK = 0x0206
}
private class MouseEvent
{
public Point Location { get; }
public MouseMessages Message { get; }
public long Time { get; }
public MouseEvent(Point location, MouseMessages message, long time)
{
Location = location;
Message = message;
Time = time;
}
}
[STAThread]
public static void Main()
{
Application.Run(new RecordForm());
}
}
今天没加注释,代码都很简单,代码即注释!如果你对鼠标钩子不熟悉,建议你把钩子那部份代码扣出来,独立封装成一个类,这样动手做一下很容易就明白这钩子原理了,非常的简单,对了,因为用到了鼠标钩子这类系统API,不同的WINDOWS系统要求可能不一样,有些WINDOWS系统可能需要你的管理员权限,那你就右键程序管理员权限执行即可。
这些不同的钩子在WINDOWS系统中可以做许许多多的事情,例如:拦截操作系统的消息,比如鼠标点击或键盘输入、修改或增强系统函数或库函数的行为、监控或记录系统或应用程序的行为。当然你还可以用在某些邪恶的目的中,比如创建键盘记录器或其他恶意软件,请最大的发挥你们的想象空间,哈哈哈,格局打开会有很多新思路。