C#全域性鉤子使用
阿新 • • 發佈:2018-12-20
最近遇到了一個需要處理鍵盤按鍵釋放訊息的問題,我在使用重寫ProcessCmdKey之後,發現其無法響應KeyUp訊息,不知是被什麼東西攔截了,查閱了網上的一些資料,使用全域性鉤子解決了這個問題,在此把過程記錄下來。
首先,在使用鉤子前我們先來了解一下要使用到的API函式。
第一步:宣告API
//安裝鉤子 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); //解除安裝鉤子 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern bool UnhookWindowsHookEx(int idHook); //繼續下一個鉤子 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); // 取得當前執行緒編號(執行緒鉤子需要用到) [DllImport("kernel32.dll")] static extern int GetCurrentThreadId();
第二步:宣告一個委託,該委託在捕獲鍵盤訊息是會自動呼叫其對應的方法
public event KeyEventHandler KeyUpEvent; public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam); static int hKeyboardHook = 0; //宣告鍵盤鉤子處理的初始值,鉤子是否安裝成功 public const int WH_KEYBOARD_LL = 13; //執行緒鍵盤鉤子監聽滑鼠訊息設為2,全域性鍵盤監聽滑鼠訊息設為13 HookProc KeyboardHookProcedure; //宣告KeyboardHookProcedure作為HookProc型別
第三步:安裝和解除安裝鉤子
//安裝鉤子 public void Start() { if (hKeyboardHook == 0) { KeyboardHookProcedure = new HookProc(KeyboardHookProc); SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0); /*關於SetWindowsHookEx的第三個引數hInstance, 如果threadId標識當前程序建立的一個執行緒,而且子程程式碼位於當前程序, hInstance必須為NULL*/ /*關於SetWindowsHookEx的第四個引數threadId,如果為全域性鉤子, 則應該設定為0,如果是執行緒鉤子,則使用GetCurrentThreadId獲取子執行緒的ID*/ //************************************ //鍵盤執行緒鉤子 //SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要監聽的執行緒idGetCurrentThreadId(), //************************************ //如果SetWindowsHookEx失敗 if (hKeyboardHook == 0) { Stop(); throw new Exception("安裝鍵盤鉤子失敗"); } } } //解除安裝鉤子 public void Stop() { bool retKeyboard = true; if (hKeyboardHook != 0) { retKeyboard = UnhookWindowsHookEx(hKeyboardHook); hKeyboardHook = 0; } if (!(retKeyboard)) throw new Exception("解除安裝鉤子失敗!"); }
第四步:鉤子 子執行緒
在這之前我們先來看下子程要用的三個引數int型別的 nCode, Int32型別的 wParam, IntPtr型別的 lParam,nCode引數是鉤子程式碼,鉤子子程使用這個引數來確定任務。wParam和lParam引數包含了訊息資訊,我們可以從中提取需要的資訊,為了將這兩個引數轉成我們更容易理解的訊息,在這定義了一個結構體(對於鍵盤來說),將wParam和lParam轉化成我們更加容易看懂的資訊。
//鍵盤結構
[StructLayout(LayoutKind.Sequential)]
public class KeyboardHookStruct
{
public int vkCode; //定一個虛擬鍵碼。該程式碼必須有一個價值的範圍1至254
public int scanCode; // 指定的硬體掃描碼的關鍵
public int flags; // 鍵標誌
public int time; // 指定的時間戳記的這個訊息
public int dwExtraInfo; // 指定額外資訊相關的資訊
}
private const int WM_KEYUP = 0x101;//KEYUP
private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
// 偵聽鍵盤事件
if (nCode >= 0 )
{
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
// 鍵盤擡起
if (KeyUpEvent != null&&wParam == WM_KEYUP)
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
//if (keyData == Keys.Up)
//{
//使用EventHandler來傳遞資料
KeyEventArgs e = new KeyEventArgs(keyData);
KeyUpEvent(this, e);
//MessageBox.Show("捕捉到了按鍵釋放");
//}
}
}
//如果返回1,則結束訊息,這個訊息到此為止,不再傳遞。
//如果返回0或呼叫CallNextHookEx函式則訊息出了這個鉤子繼續往下傳遞,也就是傳給訊息真正的接受者
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
在上面的子程裡面,我們使用EventHandler來向外部傳遞資料,那麼我們就要先宣告一個事件
public event KeyEventHandler KeyUpEvent;
至此,鉤子類已經基本完成,以下是整個類的程式碼:
class KeyboardHook
{
public event KeyEventHandler KeyUpEvent;
public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
static int hKeyboardHook = 0; //宣告鍵盤鉤子處理的初始值
public const int WH_KEYBOARD_LL = 13; //執行緒鍵盤鉤子監聽滑鼠訊息設為2,全域性鍵盤監聽滑鼠訊息設為13
HookProc KeyboardHookProcedure; //宣告KeyboardHookProcedure作為HookProc型別
//鍵盤結構
[StructLayout(LayoutKind.Sequential)]
public class KeyboardHookStruct
{
public int vkCode; //定一個虛擬鍵碼。該程式碼必須有一個價值的範圍1至254
public int scanCode; // 指定的硬體掃描碼的關鍵
public int flags; // 鍵標誌
public int time; // 指定的時間戳記的這個訊息
public int dwExtraInfo; // 指定額外資訊相關的資訊
}
//使用此功能,安裝了一個鉤子
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
//呼叫此函式解除安裝鉤子
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
//使用此功能,通過資訊鉤子繼續下一個鉤子
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
// 取得當前執行緒編號(執行緒鉤子需要用到)
[DllImport("kernel32.dll")]
static extern int GetCurrentThreadId();
public void Start()
{
// 安裝鍵盤鉤子
if (hKeyboardHook == 0)
{
KeyboardHookProcedure = new HookProc(KeyboardHookProc);
SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);
//************************************
//鍵盤執行緒鉤子
//SetWindowsHookEx(13, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要監聽的執行緒idGetCurrentThreadId(),
//************************************
//如果SetWindowsHookEx失敗
if (hKeyboardHook == 0)
{
Stop();
throw new Exception("安裝鍵盤鉤子失敗");
}
}
}
public void Stop()
{
bool retKeyboard = true;
if (hKeyboardHook != 0)
{
retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
hKeyboardHook = 0;
}
if (!(retKeyboard)) throw new Exception("解除安裝鉤子失敗!");
}
private const int WM_KEYUP = 0x101;//KEYUP
private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
// 偵聽鍵盤事件
if (nCode >= 0 )
{
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
// 鍵盤擡起
if (KeyUpEvent != null&&wParam == WM_KEYUP)
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
//if (keyData == Keys.Up)
//{
KeyEventArgs e = new KeyEventArgs(keyData);
KeyUpEvent(this, e);
//MessageBox.Show("捕捉到了按鍵釋放");
//}
}
}
//如果返回1,則結束訊息,這個訊息到此為止,不再傳遞。
//如果返回0或呼叫CallNextHookEx函式則訊息出了這個鉤子繼續往下傳遞,也就是傳給訊息真正的接受者
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
~KeyboardHook()
{
Stop();
}
}
第五步:接下來就是呼叫了。
首先我們宣告資料傳遞事件 並且初始化鉤子類
private KeyEventHandler myKeyEventHandeler = null;//按鍵鉤子
private KeyboardHook k_hook = new KeyboardHook();
接下來,寫兩個函式分別負責安裝和解除安裝鉤子,其中hook_KeyUp是鉤子捕捉到訊息後具體需要實現的方法
public void start()
{
myKeyEventHandeler = new KeyEventHandler(hook_KeyUp);
k_hook.KeyUpEvent += myKeyEventHandeler;//鉤住鍵按下
k_hook.Start();//安裝鍵盤鉤子
}
public void stop()
{
if (myKeyEventHandeler != null)
{
k_hook.KeyUpEvent -= myKeyEventHandeler;//取消按鍵事件
myKeyEventHandeler = null;
k_hook.Stop();//關閉鍵盤鉤子
}
}
private void hook_KeyUp(object sender, KeyEventArgs e)
{
// 這裡寫具體實現
textBox1.Text = "捕捉到了" + e.KeyCode.ToString() + "鍵釋放";
}