1 概述
在c++中有钩子程序,但是在C#还没有对其进行封装,所以需要自己根据实际情况调用钩子。钩子在我的理解下是,通过初始化钩子与系统中消息映射建立某种关系,当点击鼠标或者键盘,就会通过钩子中的回调函数获取信息。
钩子分为全局钩子和私有钩子
2 编写流程
a 从c++中导入,需要自己添加导入函数。
代码为:
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(HookType hook, HookProc callback, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
static extern short GetKeyState(VirtualKeys nVirtKey);
b 定义委托和事件
为了方便在注册后,hook能获取消息,可以通过委托和事件。
//SetWindowsHookEx调用。
public delegate int HookProc(int nCode, int wParam, IntPtr lParam);
//用于获取消息
private static HookProc MouseHookProcedure;
private static HookProc KeyboardHookProcedure;
事件
#region Events
/// <summary>
/// 当用户移动鼠标,按下任意鼠标按钮或滚动的车轮
/// </summary>
public event MouseEventHandler OnMouseActivity;
/// <summary>
/// 发生时,用户按下一个键
/// </summary>
public event KeyEventHandler KeyDown;
/// <summary>
/// 当用户按下并释放
/// </summary>
public event KeyPressEventHandler KeyPress;
/// <summary>
/// 当用户释放的关键
/// </summary>
public event KeyEventHandler KeyUp;
#endregion
c 定义Hooktype
public enum HookType : int
{
WH_JOURNALRECORD = 0,
WH_JOURNALPLAYBACK = 1,
WH_KEYBOARD = 2,//私有钩子定义
WH_GETMESSAGE = 3,
WH_CALLWNDPROC = 4,
WH_CBT = 5,
WH_SYSMSGFILTER = 6,
WH_MOUSE = 7,
WH_HARDWARE = 8,
WH_DEBUG = 9,
WH_SHELL = 10,
WH_FOREGROUNDIDLE = 11,
WH_CALLWNDPROCRET = 12,
WH_KEYBOARD_LL = 13,//全局钩子定义
WH_MOUSE_LL = 14
}
d hook类的定义和实现
public HookApi(bool InstallMouseHook, bool InstallKeyboardHook)
{
Start(InstallMouseHook, InstallKeyboardHook);
}
#region Hook Handling
public void Start(bool InstallMouseHook, bool InstallKeyboardHook)
{
//定义鼠标钩子函数
if (hMouseHook == 0 && InstallMouseHook)
{
//关联鼠标事件
MouseHookProcedure = new HookProc(MouseHookProc);
//IntPtr ha=Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]);
IntPtr ha = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
//注册为全局钩子,后面会讲解
hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure, ha, 0);
//hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseHookProcedure,IntPtr.Zero, 0);
if (hMouseHook == 0)
{
int errorCode = Marshal.GetLastWin32Error();
Stop(true, false, false);
throw new Win32Exception(errorCode);
//throw new Exception(errorCode.ToString());
}
}
if (hKeyboardHook == 0 && InstallKeyboardHook)
{
KeyboardHookProcedure = new HookProc(KeyboardHookProc);
//IntPtr ha = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]);
IntPtr ha = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, ha, 0);
//hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardHookProcedure,IntPtr.Zero, 0);
if (hKeyboardHook == 0)
{
int errorCode = Marshal.GetLastWin32Error();
Stop(false, true, false);
throw new Win32Exception(errorCode);
//throw new Exception(errorCode.ToString());
}
}
}
private int MouseHookProc(int nCode, int wParam, IntPtr lParam)
{
if ((nCode >= 0) && (OnMouseActivity != null))
{
MouseLLHookStruct mouseHookStruct = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));
MouseButtons button = MouseButtons.None;
short mouseDelta = 0;
switch (wParam)
{
case WM_LBUTTONDOWN:
//case WM_LBUTTONUP:
//case WM_LBUTTONDBLCLK:
button = MouseButtons.Left;
break;
case WM_RBUTTONDOWN:
//case WM_RBUTTONUP:
//case WM_RBUTTONDBLCLK:
button = MouseButtons.Right;
break;
case WM_MOUSEWHEEL:
//如果消息是WM_MOUSEWHEEL,高阶字的mouseData成员车轮三角洲。
//点击一个车轮被定义为,这是120 WHEEL_DELTA
//(value >> 16) & 0xffff; retrieves the high-order word from the given 32-bit value
mouseDelta = (short)((mouseHookStruct.mouseData >> 16) & 0xffff);
//TODO: X BUTTONS (I havent them so was unable to test)
//如果消息是WM_XBUTTONUP WM_XBUTTONDOWN,WM_XBUTTONDBLCLK,WM_NCXBUTTONDOWN,WM_NCXBUTTONUP
//或WM_NCXBUTTONDBLCLK的,高阶字指定X键被按下或释放,
//低位字被保留。这个值可以是以下值中的一个或多个。
//否则,mouseData还没有使用。
break;
}
int clickCount = 0;
if (button != MouseButtons.None)
if (wParam == WM_LBUTTONDBLCLK || wParam == WM_RBUTTONDBLCLK)
clickCount = 2;
else clickCount = 1;
MouseEventArgs e = new MouseEventArgs(button, clickCount, mouseHookStruct.pt.x, mouseHookStruct.pt.y, mouseDelta);
OnMouseActivity(this, e);
}
return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}
//键盘事件响应函数,在注册后,就可以在这函数添加响应
private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
try
{
bool handled = false;
if ((nCode >= 0) && (KeyDown != null || KeyUp != null || KeyPress != null))
{
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)
Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
if (KeyDown != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
KeyDown(this, e);
handled = handled || e.Handled;
}
if (KeyPress != null && wParam == WM_KEYDOWN)
{
bool isDownShift = ((GetKeyState(VK_SHIFT) & 0x80) == 0x80 ? true : false);
bool isDownCapslock = (GetKeyState(VK_CAPITAL) != 0 ? true : false);
byte[] keyState = new byte[256];
GetKeyboardState(keyState);
byte[] inBuffer = new byte[2];
if (ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) == 1)
{
char key = (char)inBuffer[0];
if ((isDownCapslock ^ isDownShift) && Char.IsLetter(key))
{
key = Char.ToUpper(key);
}
KeyPressEventArgs e = new KeyPressEventArgs(key);
KeyPress(this, e);
handled = handled || e.Handled;
}
}
if (KeyUp != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP))
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
KeyUp(this, e);
handled = handled || e.Handled;
}
}
if (handled)
return 1;
else
return 0;//CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
catch (Exception ex)
{
return -1;
}
}
public void Stop()
{
this.Stop(true, true, true);
}
//钩子卸载
public void Stop(bool UninstallMouseHook, bool UninstallKeyboardHook, bool ThrowExceptions)
{
if (hMouseHook != 0 && UninstallMouseHook)
{
int retMouse = UnhookWindowsHookEx(hMouseHook);
hMouseHook = 0;
if (retMouse == 0 && ThrowExceptions)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode);
}
}
if (hKeyboardHook != 0 && UninstallKeyboardHook)
{
int retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
hKeyboardHook = 0;
if (retKeyboard == 0 && ThrowExceptions)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode);
}
}
}
e 钩子函数调用
HookApi hook ;
if (HcHook == null)
{
HcHook = new HookApi(false, true);
}
HcHook.KeyDown += new KeyEventHandler(HcHook_KeyDown);
HcHook.KeyUp += new KeyEventHandler(HcHook_KeyUp);
void HcHook_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.ControlKey)
{
Cat.Log.FileLog.WriteMessage("CCVSifhtPanel中按下Ctrl");
FlagKeyborad = true;
}
}
3 注意事项
下面备注一下在使用过程中一些类库说明及注意事项:
1、AppDomain.GetCurrentThreadId()在.net 2.0中过时了,VS2005和VS2008警告这个方法已经过时,建议使用System.Threading.Thread.CurrentThread.ManagedThreadId,但实际上这两个值是不一样的。AppDomain.GetCurrentThreadId()的实际上调用Win32 API,其返回的是该线程在Windows中的ThreadId,即同这个等价:
[DllImport("kernel32")]
public static extern int GetCurrentThreadId();
而System.Threading.Thread.CurrentThread.ManagedThreadId返回的是作为一个ManagedThread在.Net CLR中的ThreadId,所以这和Windows的ThreadId是完全不同的。
2、使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。SetWindowsHookEx函数总是在Hook链的开头安装Hook子程。当指定类型的Hook监视的事件发生时,系统就调用与这个Hook关联的Hook链的开头的Hook子程。每一个Hook链中的Hook子程都决定是否把这个事件传递到下一个Hook子程。Hook子程传递事件到下一个Hook子程需要调用CallNextHookEx函数.函数签名如下:
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
代码
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->HHOOK SetWindowsHookEx(
int idHook, // 钩子的类型,即它处理的消息类型
HOOKPROC lpfn, // 钩子子程的地址指针。如果dwThreadId参数为0
// 或是一个由别的进程创建的线程的标识,
// lpfn必须指向DLL中的钩子子程。
// 除此以外,lpfn可以指向当前进程的一段钩子子程代码。
// 钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。
HINSTANCE hMod, // 应用程序实例的句柄。标识包含lpfn所指的子程的
DLL。
// 如果dwThreadId 标识当前进程创建的一个线程,
// 而且子程代码位于当前进程,hMod必须为NULL。
// 可以很简单的设定其为本应用程序的实例句柄。
DWORD dwThreadId // 与安装的钩子子程相关联的线程的标识符。
// 如果为0,钩子子程与所有的线程关联,即为全局钩子。
);
函数成功则返回钩子子程的句柄,失败返回NULL。
以上所说的钩子子程与线程相关联是指在一钩子链表中发给该线程的消息同时发送给钩子子程,且被钩子子程先处理。
3、系统钩子和线程钩子:
SetWindowsHookEx()函数的最后一个参数决定了此钩子是系统钩子还是线程钩子。
线程勾子用于监视指定线程的事件消息。线程勾子一般在当前线程或者当前线程派生的线程内。
系统勾子监视系统中的所有线程的事件消息。因为系统勾子会影响系统中所有的应用程序,所以勾子函数必须放在独立的动态链接库(DLL) 中。系统自动将包含”钩子回调函数”的DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程。
几点说明:
(1)如果对于同一事件(如鼠标消息)既安装了线程勾子又安装了系统勾子,那么系统会自动先调用线程勾子,然后调用系统勾子。
(2)对同一事件消息可安装多个勾子处理过程,这些勾子处理过程形成了勾子链。当前勾子处理结束后应把勾子信息传递给下一个勾子函数。
(3)勾子特别是系统勾子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装勾子,在使用完毕后要及时卸载。