学习之路三十八:Hook(钩子)的学习
好久没写文章了,还记得年前面试了一家公司,为了检测一下我的学习能力,给了我一个任务,做一个自动登录并自动操作菜单的程序。
花了几天的时间研究了Hook以及使用WindowsAPI操作程序的知识,现在记录一下,也算是一次温习。
一丶Hook
在我看来Hook就是监测用户操作键盘(或虚拟键盘)以及鼠标的行为,对于Hook的理解我也不是很深入,也只是一点皮毛。
1. 实现Hook的步骤
①安装钩子
②监测键盘和鼠标的操作,用来实现相应的逻辑
③卸载钩子
2.安装钩子
钩子分两种:键盘钩子和鼠标钩子,而每一种钩子又可以分为全局钩子或局部勾子。
下面是安装钩子需要的Windows Message常量(网上找的)。
public enum HookType : int
{
/// <summary>
/// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动
///条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。
///WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通
///过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook
///监视所有应用程序消息。
///
///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间
///过滤消息,这等价于在主消息循环中过滤消息。
///
///通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这
///个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循
///环里一样
/// </summary>
WH_MSGFILTER = -,
/// <summary>
/// WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这
///个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook
///来回放。WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样
///使用。WH_JOURNALRECORD是system-wide local hooks,它们不会被注射到任何行
///程地址空间
/// </summary>
WH_JOURNALRECORD = ,
/// <summary>
/// WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可
///以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠
///标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘
///事件就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定
///Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处
///理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实
///时事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它们不会被
///注射到任何行程地址空间
/// </summary>
WH_JOURNALPLAYBACK = ,
/// <summary>
/// 在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and
///WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使
///用这个Hook来监视输入到消息队列中的键盘消息
/// </summary>
WH_KEYBOARD = ,
/// <summary>
/// 应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函
///数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及
///其它发送到消息队列中的消息
/// </summary>
WH_GETMESSAGE = ,
/// <summary>
/// 监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之前调用
/// </summary>
WH_CALLWNDPROC = ,
/// <summary>
/// 在以下事件之前,系统都会调用WH_CBT Hook子过程,这些事件包括:
///1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;
///2. 完成系统指令;
///3. 来自系统消息队列中的移动鼠标,键盘事件;
///4. 设置输入焦点事件;
///5. 同步系统消息队列事件。
///Hook子过程的返回值确定系统是否允许或者防止这些操作中的一个
/// </summary>
WH_CBT = ,
/// <summary>
/// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动
///条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。
///WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通
///过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook
///监视所有应用程序消息。
///
///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间
///过滤消息,这等价于在主消息循环中过滤消息。
///
///通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这
///个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循
///环里一样
/// </summary>
WH_SYSMSGFILTER = ,
/// <summary>
/// WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。
///使用这个Hook监视输入到消息队列中的鼠标消息
/// </summary>
WH_MOUSE = ,
/// <summary>
/// 当调用GetMessage 或 PeekMessage 来从消息队列种查询非鼠标、键盘消息时
/// </summary>
WH_HARDWARE = ,
/// <summary>
/// 在系统调用系统中与其它Hook关联的Hook子过程之前,系统会调用
///WH_DEBUG Hook子过程。你可以使用这个Hook来决定是否允许系统调用与其它
///Hook关联的Hook子过程
/// </summary>
WH_DEBUG = ,
/// <summary>
/// 外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是
///激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子过程。
///WH_SHELL 共有5钟情况:
///1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁;
///2. 当Taskbar需要重画某个按钮;
///3. 当系统需要显示关于Taskbar的一个程序的最小化形式;
///4. 当目前的键盘布局状态改变;
///5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。
///
///按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接
///收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自
///己
/// </summary>
WH_SHELL = ,
/// <summary>
/// 当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE
///Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就
///会调用WH_FOREGROUNDIDLE Hook子过程
/// </summary>
WH_FOREGROUNDIDLE = ,
/// <summary>
/// 监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之后调用
/// </summary>
WH_CALLWNDPROCRET = ,
/// <summary>
/// 监视输入到线程消息队列中的键盘消息
/// </summary>
WH_KEYBOARD_LL = ,
/// <summary>
/// 监视输入到线程消息队列中的鼠标消息
/// </summary>
WH_MOUSE_LL =
}
而用的最多的也就是:WH_KEYBOARD,WH_MOUSE, WH_KEYBOARD_LL,WH_MOUSE_LL。
WH_KEYBOARD和WH_MOUSE是全局钩子,而WH_KEYBOARD_LL和WH_MOUSE_LL是针对某个线程的。
所以说安装全局还是局部钩子取决于传入的消息常量。
安装钩子需要调用的API:
/// <summary>
/// 安装勾子
/// </summary>
/// <param name="idHook">钩子类型,此处用整形的枚举表示</param>
/// <param name="hookCallBack">钩子发挥作用时的回调函数</param>
/// <param name="moudleHandle">应用程序实例的模块句柄(一般来说是你钩子回调函数所在的应用程序实例模块句柄)</param>
/// <param name="threadID">与安装的钩子子程相关联的线程的标识符
/// <remarks>如果线程ID是0则针对系统级别的,否则是针对当前线程</remarks>
/// </param>
/// <returns>返回钩子句柄</returns>
[DllImport("user32.dll")]
public static extern int SetWindowsHookEx(int idHook, HookProcCallBack hookCallBack, IntPtr moudleHandle, int threadID); public delegate int HookProcCallBack(int nCode, int wParam, IntPtr lParam);
☆:上面方法的第二个需要是个委托参数,必须把它设置为静态变量,因为监测钩子相当于一个定时器一直在跑,如果委托变量不是静态的话,会被GC给回收掉的。
3.监测键盘和鼠标行为
键盘操作分为:keyDown,keyPress,keyUp;鼠标操作分为:rightClick,leftClick,doubleClick,wheel,move。
所以为了要监测上面的所有行为,需要使用事件来实现。
4.卸载钩子
主要还是调用API就可以了。
/// <summary>
/// 卸载勾子
/// </summary>
/// <param name="handle">要取消的钩子的句柄</param>
/// <returns>卸载钩子是否成功</returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int handle);
5.HookManager
发现写不下去,不知道该讲什么了,很多细节上都没有讲到,比如每个参数的含义,怎么调用等等,算是给出个思路,我也是下载了很多源码摸索过来的。
给出主要实现代码:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms; using SharpCommon.Windows; /* * 2014-1-28 完善第一个版本
*
* 1.关于KeyPress的解释
* 在控件有焦点的情况下按下键时发生。
* 键事件按下列顺序发生:
KeyDown
KeyPress
KeyUp 非字符键不会引发 KeyPress 事件;但非字符键却可以引发 KeyDown 和 KeyUp 事件。
使用 KeyChar 属性在运行时对键击进行取样,并且使用或修改公共键击的子集。
若要仅在窗体级别处理键盘事件而不允许其他控件接收键盘事件,
* 请将窗体的 KeyPress 事件处理方法中的 KeyPressEventArgs.Handled 属性设置为 true。
*
* 摘自MSDN上的说明
* KeyPressEventArgs 指定在用户按键时撰写的字符。例如,当用户按 Shift + K 时,KeyChar 属性返回一个大写字母 K。
当用户按下任意键时,发生 KeyPress 事件。与 KeyPress 事件紧密相关的两个事件为 KeyUp 和 KeyDown。
* 当用户按下某个键时,KeyDown 事件先于每个 KeyPress 事件发生;当用户释放某个键时发生 KeyUp 事件。
* 当用户按住某个键时,每次字符重复时,KeyDown 和 KeyPress 事件也都重复发生。一个 KeyUp 事件在释放按键时生成。 KeyPressEventArgs 随着 KeyPress 事件的每次发生而被传递。
* KeyEventArgs 随着 KeyDown 和 KeyUp 事件的每次发生而被传递。
* KeyEventArgs 指定是否有任一个组合键(Ctrl、Shift 或 Alt)在另一个键按下的同时也曾按下。
* 此修饰符信息也可以通过 Control 类的 ModifierKeys 属性获得。 将 Handled 设置为 true,以取消 KeyPress 事件。这可防止控件处理按键。 注意注意:
有些控件将会在 KeyDown 上处理某些击键。
* 例如,RichTextBox 在调用 KeyPress 前处理 Enter 键。
* 在这种情况下,您无法取消 KeyPress 事件,而是必须从 KeyDown 取消击键。
*
*
* 2014-1-28 1:00 PM
* 1. 完成了对组合键的监测代码,通过获取KeyState来判断是否按了组合键 */ namespace SharpCommon.Hook
{
public sealed class HookManager
{
#region Event And Field
public event CustomKeyEventHandler KeyUp;
public event CustomKeyEventHandler KeyDown;
public event CustomKeyEventHandler KeyPress; public event MouseEventHandler MouseMove;
public event MouseEventHandler MouseWheel;
public event MouseEventHandler LeftMouseClickUp;
public event MouseEventHandler RightMouseClickUp;
public event MouseEventHandler LeftMouseClickDown;
public event MouseEventHandler RightMouseClickDown;
public event MouseEventHandler LeftMouseDoubleClick;
public event MouseEventHandler RightMouseDoubleClick; private static int _mouseHookHandle;
private static int _keyboardHookHandlel; private static HookProcCallBack _mouseHookCallBack;
private static HookProcCallBack _keyboardHookCallBack; private static readonly HookManager _instance = new HookManager(); private static readonly int _currentThreadID = AppDomain.GetCurrentThreadId();
private static readonly IntPtr _currentMoudleHandle = WindowsAPI.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName); #endregion #region Instance private HookManager()
{ } public static HookManager Instance
{
get { return _instance; }
} #endregion #region Install Hook /// <summary>
/// Install the hook.
/// </summary>
/// <param name="installType">Select the hook install type.</param>
public void InstallHook(HookInstallType installType = HookInstallType.MouseAndKeyBoard)
{
switch (installType)
{
case HookInstallType.Mouse:
this.InstallMouseHook();
break;
case HookInstallType.KeyBoard:
this.InstallKeyBoardHook();
break;
case HookInstallType.MouseAndKeyBoard:
this.InstallMouseHook();
this.InstallKeyBoardHook();
break;
}
} #endregion #region Mouse Hook Monitor /// <summary>
/// Install the mouse hook.
/// Default install mouse global hook - [14];
/// </summary>
/// <param name="hookType">Select mouse hook type.</param>
public void InstallMouseHook(HookType hookType = HookType.WH_MOUSE_LL)
{
if (_mouseHookHandle == default(int))
{
_mouseHookCallBack = new HookProcCallBack(this.MouseHookCallBack);
if (hookType == HookType.WH_MOUSE)
{
_mouseHookHandle = HookAPI.SetWindowsHookEx((int)hookType, _mouseHookCallBack, IntPtr.Zero, _currentThreadID);
}
else
{
_mouseHookHandle = HookAPI.SetWindowsHookEx((int)hookType, _mouseHookCallBack, _currentMoudleHandle, );
}
this.CheckHandleIsZero(_mouseHookHandle);
}
} private int MouseHookCallBack(int nCode, int wParam, IntPtr lParam)
{
MouseButtons mouseOperation = MouseButtons.None;
Point mousePoint = (Point)Marshal.PtrToStructure(lParam, typeof(Point)); switch (wParam)
{
case (int)WindowsMessage.WM_LBUTTONDOWN:
mouseOperation = MouseButtons.Left;
this.InvokeMouseEvent(this.LeftMouseClickDown, mouseOperation, mousePoint);
break;
case (int)WindowsMessage.WM_LBUTTONUP:
mouseOperation = MouseButtons.Left;
this.InvokeMouseEvent(this.LeftMouseClickUp, mouseOperation, mousePoint);
break;
case (int)WindowsMessage.WM_LBUTTONDBLCLK:
mouseOperation = MouseButtons.Left;
this.InvokeMouseEvent(this.LeftMouseDoubleClick, mouseOperation, mousePoint);
break;
case (int)WindowsMessage.WM_RBUTTONDOWN:
mouseOperation = MouseButtons.Right;
this.InvokeMouseEvent(this.RightMouseClickDown, mouseOperation, mousePoint);
break;
case (int)WindowsMessage.WM_RBUTTONUP:
mouseOperation = MouseButtons.Right;
this.InvokeMouseEvent(this.RightMouseClickUp, mouseOperation, mousePoint);
break;
case (int)WindowsMessage.WM_RBUTTONDBLCLK:
mouseOperation = MouseButtons.Right;
this.InvokeMouseEvent(this.RightMouseDoubleClick, mouseOperation, mousePoint);
break;
case (int)WindowsMessage.WM_MOUSEMOVE:
this.InvokeMouseEvent(this.MouseMove, mouseOperation, mousePoint);
break;
case (int)WindowsMessage.WM_MOUSEWHEEL:
this.InvokeMouseEvent(this.MouseWheel, mouseOperation, mousePoint);
break;
} return HookAPI.CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam);
} private void InvokeMouseEvent(MouseEventHandler mouseEvent, MouseButtons mouseButton, Point point)
{
if (mouseEvent != null)
{
MouseEventArgs mouseArgs = new MouseEventArgs(mouseButton, , point.X, point.Y, );
mouseEvent(this, mouseArgs);
}
} #endregion #region KeyBoaed Hook Monitor /// <summary>
/// Install the keyboard hook.
/// Default install keyboard global hook - [13].
/// </summary>
/// <param name="hookType">Select keyboard hook type.</param>
public void InstallKeyBoardHook(HookType hookType = HookType.WH_KEYBOARD_LL)
{
if (_keyboardHookHandlel == default(int))
{
_keyboardHookCallBack = new HookProcCallBack(this.KeyBoradHookCallBack);
if (hookType == HookType.WH_KEYBOARD)
{
_keyboardHookHandlel = HookAPI.SetWindowsHookEx((int)hookType, _keyboardHookCallBack, IntPtr.Zero, _currentThreadID);
}
else
{
_keyboardHookHandlel = HookAPI.SetWindowsHookEx((int)hookType, _keyboardHookCallBack, _currentMoudleHandle, );
}
this.CheckHandleIsZero(_keyboardHookHandlel);
}
} private int KeyBoradHookCallBack(int nCode, int wParam, IntPtr lParam)
{
if (nCode >= )
{
CustomKeyBoard keyInfo = (CustomKeyBoard)Marshal.PtrToStructure(lParam, typeof(CustomKeyBoard)); if (this.KeyDown != null
&& (wParam == (int)WindowsMessage.WM_KEYDOWN || wParam == (int)WindowsMessage.WM_SYSKEYDOWN))
{
this.InvokeKeyBoardEvent(this.KeyDown, (Keys)keyInfo.VirtualKeyCode);
} if (this.KeyPress != null && wParam == (int)WindowsMessage.WM_KEYDOWN)
{
this.InvokeKeyBoardEvent(this.KeyPress, (Keys)keyInfo.VirtualKeyCode);
} if (this.KeyUp != null
&& (wParam == (int)WindowsMessage.WM_KEYUP || wParam == (int)WindowsMessage.WM_SYSKEYUP))
{
this.InvokeKeyBoardEvent(this.KeyUp, (Keys)keyInfo.VirtualKeyCode);
}
} return HookAPI.CallNextHookEx(_keyboardHookHandlel, nCode, wParam, lParam);
} private void InvokeKeyBoardEvent(CustomKeyEventHandler keyEvent, Keys keyData)
{
CustomKeyEventArgs customKeyArgs = new CustomKeyEventArgs(keyData);
keyEvent(this, customKeyArgs);
} #endregion #region Common private void CheckHandleIsZero(int handle)
{
if (handle == )
{
int errorID = Marshal.GetLastWin32Error();
throw new Win32Exception(errorID);
}
} public void UninstallHook()
{
if (_mouseHookHandle != default(int))
{
if (HookAPI.UnhookWindowsHookEx(_mouseHookHandle))
{
_mouseHookHandle = default(int);
}
}
if (_keyboardHookHandlel != default(int))
{
if (HookAPI.UnhookWindowsHookEx(_keyboardHookHandlel))
{
_keyboardHookHandlel = default(int);
}
}
} #endregion
}
}
全部代码:下载
好了就这么多了,已同步至:个人文章目录索引
学习之路三十八:Hook(钩子)的学习的更多相关文章
- 学习之路三十九:新手学习 - Windows API
来到了新公司,一开始就要做个程序去获取另外一个程序里的数据,哇,挑战性很大. 经过两周的学习,终于搞定,主要还是对Windows API有了更多的了解. 文中所有的消息常量,API,结构体都整理出来了 ...
- FastAPI 学习之路(十八)表单与文件
系列文章: FastAPI 学习之路(一)fastapi--高性能web开发框架 FastAPI 学习之路(二) FastAPI 学习之路(三) FastAPI 学习之路(四) FastAPI 学习之 ...
- Python小白学习之路(十八)—【内置函数三】
一.对象操作 help() 功能:返回目标对象的帮助信息 举例: print(help(input)) #执行结果 Help on built-in function input in module ...
- Spark学习之路 (十八)SparkSQL简单使用
一.SparkSQL的进化之路 1.0以前: Shark 1.1.x开始: SparkSQL(只是测试性的) SQL 1.3.x: SparkSQL(正式版本)+Dataframe 1.5.x: S ...
- Spark学习之路 (十八)SparkSQL简单使用[转]
SparkSQL的进化之路 1.0以前: Shark 1.1.x开始: SparkSQL(只是测试性的) SQL 1.3.x: SparkSQL(正式版本)+Dataframe 1.5.x: Spar ...
- Dynamic CRM 2013学习笔记(三十八)流程1 - 操作(action)开发与配置详解
CRM 2013 里流程有4个类别:操作(action).业务流程(business process flow).对话(dialog)和工作流(workflow).它们都是从 setting –> ...
- 学习之路三十二:VS调试的简单技巧
这段时间园子里讲了一些关于VS的快捷键以及一些配置技巧,挺好的,大家一起学习,一起进步. 这段时间重点看了一下关于VS调试技巧方面的书,在此记录一下学习的内容吧,主要还是一些比较浅显的知识. 1. 调 ...
- 学习之路三十五:Android和WCF通信 - 大数据压缩后传输
最近一直在优化项目的性能,就在前几天找到了一些资料,终于有方案了,那就是压缩数据. 一丶前端和后端的压缩和解压缩流程 二丶优点和缺点 优点:①字符串的压缩率能够达到70%-80%左右 ②字符串数量更少 ...
- Hive学习之路 (十八)Hive的Shell操作
一.Hive的命令行 1.Hive支持的一些命令 Command Description quit Use quit or exit to leave the interactive shell. s ...
随机推荐
- 如果我用C#来输出99表
题目:参见这个链接,简单点说就是在控制台输出一个99乘方表. 无聊想了个C#版本的解答: private static void Print(int n) { var s = Enumerable.R ...
- 仿花田:内部相亲网站 意中人(Asp.net MVC,Bootstrap2)
起因: 那是七月份了,看见单身的同事在上花田网,当时觉得风格比较清新,还没有世纪佳缘等那些网站那么商业化,加上又看到了bootrstrap,于是就想做个demo出来玩玩.中间自己又在做其他的事情,和w ...
- MVC3不能正确识别JSON中的Enum枚举值
一.背景 在MVC3项目里,如果Action的参数中有Enum枚举作为对象属性的话,使用POST方法提交过来的JSON数据中的枚举值却无法正确被识别对应的枚举值. 二.Demo演示 为了说明问题,我使 ...
- 编写高质量代码改善C#程序的157个建议读书笔记【1-10】
开篇 学生时代,老师常说,好记性不如烂笔头,事实上确实如此,有些知识你在学习的时候确实滚瓜烂熟,但是时间一长又不常用了,可能就生疏了,甚至下次有机会使用到的时候,还需要上网查找资料,所以,还不如常常摘 ...
- 深入理解c++构造函数, 复制构造函数和赋值函数重载(operator=)
注 以下代码编译及运行环境均为 Xcode 6.4, LLVM 6.1 with GNU++11 support, Mac OS X 10.10.2 调用时机 看例子 // // main.cpp / ...
- AspNet MVC 缓存
服务端缓存技术 请求域内的缓存 每个Asp.Net请求都会在Asp.Net框架中创建一个新的System.Web.HttpContext对象(HttpContext对象封装有关个别 HTTP 请求的所 ...
- css圆环百分比
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 关于SQL SERVER的N前缀的理解
加N前缀指定后面的字符串为UNICODE常量, SQL Server 的 标准中的国家字符数据类型.SQL 使用前缀字符 N 标识这些数据类型及其值. , ),使用 个 个 Unicode字符时,使用 ...
- HTML标签简明学习一
!-- ... -- html注释 浏览器不对其中的内容解析,可以用来调试及书写释意 <!-- 动不动就被注释 --> !DOCTYPE 声明文件类型 一般大写,必须位于文档首行,浏览器根 ...
- 使用socket.io开发简单群聊功能
1.新建package.json文件: { "name": "socket-chat-example", "version": " ...