C#全局键盘监听(Hook)
一.為什么需要全局鍵盤監(jiān)聽?
在某些情況下應(yīng)用程序需要實現(xiàn)快捷鍵執(zhí)行特定功能,例如大家熟知的QQ截圖功能Ctrl+Alt+A快捷鍵,只要QQ程序在運行(無論是擁有焦點還是處于后臺運行狀態(tài)),都可以按下快捷鍵使用此功能...
這個時候在程序中添加鍵盤監(jiān)聽肯定不能滿足需求了,當用戶焦點不在App上時(如最小化,或者用戶在處理其它事物等等)鍵盤監(jiān)聽就失效了
二.怎樣才能實現(xiàn)全局鍵盤監(jiān)聽?
這里需要用到Windows API,源碼如下:(可以作為一個工具類[KeyboardHook.cs]收藏起來)
[特別說明:下面的源碼來自某網(wǎng)友,很久以前收集的,也找不到出處了,如有版權(quán)問題請聯(lián)系我]
using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Windows.Forms; using System.Reflection;namespace 壁紙管家 {/// <summary>/// 鍵盤鉤子/// [以下代碼來自某網(wǎng)友,并非本人原創(chuàng)]/// </summary>class KeyboardHook{public event KeyEventHandler KeyDownEvent;public event KeyPressEventHandler KeyPressEvent;public event KeyEventHandler KeyUpEvent;public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);static int hKeyboardHook = 0; //聲明鍵盤鉤子處理的初始值//值在Microsoft SDK的Winuser.h里查詢public const int WH_KEYBOARD_LL = 13; //線程鍵盤鉤子監(jiān)聽鼠標消息設(shè)為2,全局鍵盤監(jiān)聽鼠標消息設(shè)為13HookProc KeyboardHookProcedure; //聲明KeyboardHookProcedure作為HookProc類型//鍵盤結(jié)構(gòu) [StructLayout(LayoutKind.Sequential)]public class KeyboardHookStruct{public int vkCode; //定一個虛擬鍵碼。該代碼必須有一個價值的范圍1至254public int scanCode; // 指定的硬件掃描碼的關(guān)鍵public int flags; // 鍵標志public int time; // 指定的時間戳記的這個訊息public int dwExtraInfo; // 指定額外信息相關(guān)的信息}//使用此功能,安裝了一個鉤子[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);//調(diào)用此函數(shù)卸載鉤子[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]public static extern bool UnhookWindowsHookEx(int idHook);//使用此功能,通過信息鉤子繼續(xù)下一個鉤子[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();//使用WINDOWS API函數(shù)代替獲取當前實例的函數(shù),防止鉤子失效[DllImport("kernel32.dll")]public static extern IntPtr GetModuleHandle(string name);public void Start(){// 安裝鍵盤鉤子if (hKeyboardHook == 0){KeyboardHookProcedure = new HookProc(KeyboardHookProc);hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);//hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);//************************************ //鍵盤線程鉤子 //SetWindowsHookEx( 2,KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要監(jiān)聽的線程idGetCurrentThreadId(),//鍵盤全局鉤子,需要引用空間(using System.Reflection;) //SetWindowsHookEx( 13,MouseHookProcedure,Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),0); // //關(guān)于SetWindowsHookEx (int idHook, HookProc lpfn, IntPtr hInstance, int threadId)函數(shù)將鉤子加入到鉤子鏈表中,說明一下四個參數(shù): //idHook 鉤子類型,即確定鉤子監(jiān)聽何種消息,上面的代碼中設(shè)為2,即監(jiān)聽鍵盤消息并且是線程鉤子,如果是全局鉤子監(jiān)聽鍵盤消息應(yīng)設(shè)為13, //線程鉤子監(jiān)聽鼠標消息設(shè)為7,全局鉤子監(jiān)聽鼠標消息設(shè)為14。lpfn 鉤子子程的地址指針。如果dwThreadId參數(shù)為0 或是一個由別的進程創(chuàng)建的 //線程的標識,lpfn必須指向DLL中的鉤子子程。 除此以外,lpfn可以指向當前進程的一段鉤子子程代碼。鉤子函數(shù)的入口地址,當鉤子鉤到任何 //消息后便調(diào)用這個函數(shù)。hInstance應(yīng)用程序?qū)嵗木浔俗R包含lpfn所指的子程的DLL。如果threadId 標識當前進程創(chuàng)建的一個線程,而且子 //程代碼位于當前進程,hInstance必須為NULL。可以很簡單的設(shè)定其為本應(yīng)用程序的實例句柄。threaded 與安裝的鉤子子程相關(guān)聯(lián)的線程的標識符//如果為0,鉤子子程與所有的線程關(guān)聯(lián),即為全局鉤子//************************************ //如果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("卸載鉤子失敗!");}//ToAscii職能的轉(zhuǎn)換指定的虛擬鍵碼和鍵盤狀態(tài)的相應(yīng)字符或字符[DllImport("user32")]public static extern int ToAscii(int uVirtKey, //[in] 指定虛擬關(guān)鍵代碼進行翻譯。 int uScanCode, // [in] 指定的硬件掃描碼的關(guān)鍵須翻譯成英文。高階位的這個值設(shè)定的關(guān)鍵,如果是(不壓)byte[] lpbKeyState, // [in] 指針,以256字節(jié)數(shù)組,包含當前鍵盤的狀態(tài)。每個元素(字節(jié))的數(shù)組包含狀態(tài)的一個關(guān)鍵。如果高階位的字節(jié)是一套,關(guān)鍵是下跌(按下)。在低比特,如果設(shè)置表明,關(guān)鍵是對切換。在此功能,只有肘位的CAPS LOCK鍵是相關(guān)的。在切換狀態(tài)的NUM個鎖和滾動鎖定鍵被忽略。byte[] lpwTransKey, // [out] 指針的緩沖區(qū)收到翻譯字符或字符。 int fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise. //獲取按鍵的狀態(tài)[DllImport("user32")]public static extern int GetKeyboardState(byte[] pbKeyState);[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]private static extern short GetKeyState(int vKey);private const int WM_KEYDOWN = 0x100;//KEYDOWN private const int WM_KEYUP = 0x101;//KEYUPprivate const int WM_SYSKEYDOWN = 0x104;//SYSKEYDOWNprivate const int WM_SYSKEYUP = 0x105;//SYSKEYUPprivate int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam){// 偵聽鍵盤事件if ((nCode >= 0) && (KeyDownEvent != null || KeyUpEvent != null || KeyPressEvent != null)){KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));// raise KeyDownif (KeyDownEvent != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)){Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;KeyEventArgs e = new KeyEventArgs(keyData);KeyDownEvent(this, e);}//鍵盤按下if (KeyPressEvent != null && wParam == WM_KEYDOWN){byte[] keyState = new byte[256];GetKeyboardState(keyState);byte[] inBuffer = new byte[2];if (ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) == 1){KeyPressEventArgs e = new KeyPressEventArgs((char)inBuffer[0]);KeyPressEvent(this, e);}}// 鍵盤抬起 if (KeyUpEvent != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)){Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;KeyEventArgs e = new KeyEventArgs(keyData);KeyUpEvent(this, e);}}//如果返回1,則結(jié)束消息,這個消息到此為止,不再傳遞。//如果返回0或調(diào)用CallNextHookEx函數(shù)則消息出了這個鉤子繼續(xù)往下傳遞,也就是傳給消息真正的接受者 return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);}~KeyboardHook(){Stop();}} }三.如何使用上面的工具類?
//0.準備工作 //把上面的工具類添加到項目中//1.首先導入需要的命名空間 using System.Runtime.InteropServices; //調(diào)用WINDOWS API函數(shù)時要用到 using Microsoft.Win32; //寫入注冊表時要用到//2.安裝Hook,在程序入口中寫上下面的代碼(本例中用了WinForm,在Form的構(gòu)造方法中安裝Hook即可) //安裝鍵盤鉤子 k_hook = new KeyboardHook(); k_hook.KeyDownEvent += new KeyEventHandler(hook_KeyDown);//鉤住鍵按下 k_hook.Start();//安裝鍵盤鉤子//3.判斷輸入鍵值(實現(xiàn)KeyDown事件) private void hook_KeyDown(object sender, KeyEventArgs e){//判斷按下的鍵(Alt + A)if (e.KeyValue == (int)Keys.A && (int)Control.ModifierKeys == (int)Keys.Alt){System.Windows.Forms.MessageBox.Show("按下了指定快捷鍵組合");} } //注意幾種不同的鍵值判斷: //1>.單普通鍵(例如A) //2>.單控制鍵+單普通鍵(例如Ctrl+A) //3>.多控制鍵+單普通鍵(例如Ctrl+Alt+A) //上面的代碼中演示了2,其它情況以此類推,無非就是添幾個條件再&&起來就好3.搞定了,開始測試四.使用全局鍵盤監(jiān)聽需要注意的問題(請讀者朋友務(wù)必看看)
1.在應(yīng)用程序中使用全局鍵盤監(jiān)聽,會被360發(fā)現(xiàn),彈窗提示用戶“有程序正在監(jiān)聽鍵盤輸入,是否阻止?”
所以如果程序中必須要用Hook應(yīng)該告訴用戶不會泄露其信息等等
或者直接把App提交給360審核
否則殺軟的提示會對用戶體驗造成極大的影響
P.S.本機只在360安全衛(wèi)士環(huán)境下做了測試,其它殺軟未知,應(yīng)該也會提示類似的危險信息,故慎重使用Hook
轉(zhuǎn)載于:https://www.cnblogs.com/ayqy/p/3636427.html
總結(jié)
以上是生活随笔為你收集整理的C#全局键盘监听(Hook)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sqoop 工具速查表(中文版)--转
- 下一篇: RPM vs SRPM