本文采用两个案例说明键盘钩子的使用,案例一是使用线程钩子,案例2是系统钩子(实现跨程序获取键值)
案例一:线程钩子
该例子实现的效果是:拦截键盘的输入,如果输入的是B键或者是b键,就弹出消息框来表示一下拦截到了
步骤:
1 新建Winform应用程序,工程命名为HookDemo,把默认生成的窗体名称改为HookTest
2 新建一个类,名为Hook
编写代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; //引入的命名空间
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; //引入的命名空间
namespace HookDemo
{
internal enum HookType {
Keyboard=2 //键盘钩子类型
}
//定义委托,该委托的参数是固定这样写
internal delegate IntPtr HookProc(int code, IntPtr wparam, IntPtr lparam);
public class Hook
{
//记录下一个Hook编号
static IntPtr _nextHookPtr;
//获取当前线程编号
[DllImport("kernel32.dll")]
static extern int GetCurrentThreadId();
//卸载钩子
[DllImport("User32.dll")]
internal extern static void UnhookWindowsHookEx(IntPtr handle);
//安装钩子
[DllImport("User32.dll")]
internal extern static IntPtr SetWindowsHookEx(int idHook,[MarshalAs(UnmanagedType.FunctionPtr)]HookProc Ipfn,IntPtr hanstance,int threadID);
//获取下一个钩子
[DllImport("User32.dll")]
internal extern static IntPtr CallNextHookEx(IntPtr handle,int code,IntPtr wparam,IntPtr Iparam);
//委托回调的方法
IntPtr MyHookProc(int code,IntPtr wparam,IntPtr Iparam) {
if (code < 0)
{
//让后面的程序处理该消息
return CallNextHookEx(_nextHookPtr, code, wparam, Iparam);
}
//用户输入的是b或者B
if (wparam.ToInt32() == 98 || wparam.ToInt32() == 66)
{
//设置文本输入框为a
MessageBox.Show("我拦截到B或者b");
//该消息结束
return (IntPtr)1;
}
else
{
//让后面的程序处理该消息
return IntPtr.Zero;
}
}
/// <summary>
/// 从外部调用设置钩子
/// </summary>
public void SetHook() {
//已经设置过钩子了,不能重复设置
if(_nextHookPtr!=IntPtr.Zero){
return;
}
//设置钩子委托回调函数(委托方法注册)
HookProc myhookProc = new HookProc(MyHookProc);
//把该钩子加到Hook链中
_nextHookPtr = SetWindowsHookEx((int)HookType.Keyboard,myhookProc,IntPtr.Zero,GetCurrentThreadId());
}
/// <summary>
/// 从外部调用卸载钩子
/// </summary>
public void UnHook() {
if(_nextHookPtr!=IntPtr.Zero){
//从Hook链中取消
UnhookWindowsHookEx(_nextHookPtr);
}
_nextHookPtr = IntPtr.Zero;
}
}
}
3 找到默认生成的窗体,并找到该窗体的FormClosing事件函数,如下图:
4 编写默认窗体下的类HookTest如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace HookDemo
{
public partial class HookTest : Form
{
Hook hook = new Hook();
public HookTest()
{
InitializeComponent();
//设置钩子
hook.SetHook();
}
//页面关闭事件函数
private void HookTest_FormClosing(object sender, FormClosingEventArgs e)
{
//卸载钩子
hook.UnHook();
}
}
}
5 运行程序,并按键盘的B键,输出如下图:
案例二:系统钩子
先看一下例子的效果:
拓展:如果把黑窗体隐藏,然后设置为自启动,同时定时判断浏览器输入的url是否为某个url,如果是则进行抓取然后写到自己的数据库,你说能干什么坏事,呵呵,当然这只是想想而已
步骤如下:
1 新建Winform应用程序,工程命名为跨程序拦截输入的字符串
2 新建一个类,名为SystemHook
代码如下:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace 跨程序拦截输入的字符串
{
class SystemHook
{
// 设置钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, HookHandlerDelegate lpfn, IntPtr hmod, uint dwThreadID);
// 卸载钩子
[DllImport("user32.dll")]
public static extern bool UnhookWindowsHookEx(IntPtr idHook);
// 获取模块句柄
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(String modulename);
public const int WM_KEYDOWN = 0x0100;
public const int WH_KEYBOARD_LL = 13;
public const int WM_SYSKEYDOWN = 0x0104;
public struct KBDLLHOOKSTRUCT
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}
public delegate int HookHandlerDelegate(int nCode, IntPtr wparam, ref KBDLLHOOKSTRUCT lparam);
//钩子回掉委托实例
private static HookHandlerDelegate proc;
//钩子句柄
private static IntPtr hKeyboardHook;
private static int HookCallback(int nCode, IntPtr wparam, ref KBDLLHOOKSTRUCT lparam)
{
if (
nCode >= 0
&&
(wparam == (IntPtr)WM_KEYDOWN
||
wparam == (IntPtr)WM_SYSKEYDOWN)
)
{
//Console.WriteLine(lparam.vkCode);
if (lparam.vkCode == 13)
{
Console.WriteLine("按下回车键");
return 0;
}
else if (lparam.vkCode == 8)
{
Console.WriteLine("按下backspace键");
return 0;
}
else if (lparam.vkCode == 20)
{
Console.WriteLine("按下大小写切换键");
return 0;
}
else if (lparam.vkCode ==160||lparam.vkCode==161)
{
Console.WriteLine("按下shift键");
return 0;
}
else if (lparam.vkCode == 162)
{
Console.WriteLine("按下ctrl键");
return 0;
}
else if (lparam.vkCode == 190)
{
Console.Write(".");
return 0;
}
byte[] array = new byte[1];
array[0] = (byte)(Convert.ToInt32(lparam.vkCode)); //ASCII码强制转换二进制
Console.Write(Convert.ToString(System.Text.Encoding.UTF8.GetString(array)).ToLower());
}
return 0;
}
public static void HookStart()
{
if (hKeyboardHook == IntPtr.Zero)
{
// 创建HookProc实例
proc = new HookHandlerDelegate(HookCallback);
using (Process curPro = Process.GetCurrentProcess())
using (ProcessModule curMod = curPro.MainModule)
{
//定义全局钩子
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curMod.FileName), 0);
}
if (hKeyboardHook == IntPtr.Zero)
{
HookStop();
throw new Exception("钩子设置失败");
}
}
}
public static void HookStop()
{
bool retKeyboard = true;
if (hKeyboardHook != IntPtr.Zero)
{
retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
hKeyboardHook = IntPtr.Zero;
}
if (!(retKeyboard)) throw new Exception("卸载钩子失败");
}
}
}
3 调用使用,在默认的窗台Form1中输入代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace 跨程序拦截输入的字符串
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing);
SystemHook.HookStart();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
SystemHook.HookStop();
}
}
}
运行即可得到前面所展示的效果图