国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > 綜合 > 正文

C#+低級Windows API鉤子攔截鍵盤輸入

2024-07-21 02:26:02
字體:
來源:轉載
供稿:網友
 一. 簡介

  貓和嬰兒有很多共同之處。他們都喜歡吃家中養植的植物,都非常討厭關門。他們也都愛玩弄你的鍵盤,結果是,你正發送給你的老板的電子郵件可能是以半截句子發送出去的,你的excel帳戶也被加入了一些亂七八糟的內容,并且你還沒有注意到,當打開windows資源管理器時,若干文件已經被移到了回收站!

  其解決方案是,開發一個應用程序實現如下功能:只要鍵盤處于"威脅狀態"你就可以進行切換,并確保任何鍵盤輸入活動都不會造成危害。本文想展示如何使用一種低級windows api鉤子在一個c#應用程序中實現鍵盤"控制"。下圖是本文示例程序的一個運行快照。

  二. 背景

  其實,已經存在許多有關于windows鉤子的文章和示例代碼,并且已經有人編寫過與本文幾乎一樣的c++示例程序。然而,當我搜索相應的c#應用程序的源碼時,卻找到極少的.net示例,而且沒有一個程序能夠提供一個方便的自包含的c#類。

  .net框架能夠使你以托管方式來存取你最常使用的鍵盤事件(通過keypress,keyup和keydown)。遺憾的是,這些事件都不能被用來停止windows組合鍵(如alt+tab或windows"開始"鍵),從而允許用戶"遠離"某一個應用程序。

  本文的想法在操作系統級上捕獲鍵盤事件而不是通過框架級來實現。為此,應用程序需要使用windows api函數來把它自身添加到應用程序"鉤子鏈"中以監聽來自操作系統的鍵盤消息。當它收到這種類型的消息時,該應用程序能夠選擇性地傳遞消息,或者進行正常處理,或者"鎮壓"它以便不再有其它應用程序(包括windows)來影響它。本文正是想解釋其實現機理。

  然而,請注意,本文中的代碼僅適用于基于nt版本的windows(nt,2000和xp),并且無法使用這個方法來停用ctrl+alt+delete。有關于如何實現這一點,你可以參考msdn有關資料。

  三. 使用代碼

  為了易于使用,我在本文中提供了兩個獨立的zip文件。一個僅包含keyboardhook類,這是本文介紹的重點。另一個是一個完整的微軟visual c# 2005 express edition應用程序工程,名叫"baby keyboard bash",它實現顯示擊鍵的名字或彩色的形狀以響應于擊鍵。

  四. 實例化類

  鍵盤鉤子是通過keyboard.cs中的keyboardhook類來建立和管理的。這個類實現了idisposable接口,因此,實例化它的最簡單的方法是在應用程序的main()方法中使用using關鍵字來封裝application.run()調用。這將確保只要該應用程序開始即建立鉤子并且,更重要的是,當該應用程序結束時立即使這個鉤子失效。

  這個類引發一個事件來警告應用程序已經有鍵被按下,因此主表單能夠存取在main()方法中創建的keyboardhook實例就顯得非常重要;最簡單的方法是把這個實例存儲在一個公共成員變量中。

  keyboardhook提供了三種構造器來啟用或禁用某些設置:

  ?? keyboardhook():捕獲所有擊鍵,沒有任何內容傳遞到windows或另外的應用程序。

  ?? keyboardhook(string param):把參數串轉換為parameters枚舉中的值之一,然后調用下面的構造器:

  ?? keyboardhook(keyboardhook.parameters enum):根據從parameters枚舉中選擇的值的不同,分別啟動下列設置:

   o parameters.allowalttab:允許用戶使用alt+tab切換到另外的應用程序。

   o parameters.allowwindowskey:允許用戶使用ctrl+esc或一種windows鍵存取任務欄和開始菜單。

   o parameters.allowalttabandwindows:啟用alt+tab,ctrl+esc和windows鍵。

   o parameters.passallkeystonextapp:如果該參數為true,那么所有的擊鍵將被傳遞給任何其它監聽應用程序(包括windows)。

  當擊鍵繼續被鍵盤鉤子捕獲時,啟用alt+tab和/或windows鍵允許實際使用該計算機者切換到另一個應用程序并且使用鼠標與之交互。passallkeystonextapp設置有效地禁用了擊鍵捕獲;這個類也是建立一個低級鍵盤鉤子并且引發它的keyintercepted事件,但是它還負責把鍵盤事件傳遞到另一個監聽程序。

  因此,實例化該類以捕獲所有擊鍵的方法如下:

public static keyboardhook kh;
[stathread]
static void main()
{
 //其它代碼
 using (kh = new keyboardhook())
 {
  application.run(new form1());
 }

  五. 處理keyintercepted事件

  當一外鍵被按下時,這個keyboardhook類激活一個包含一些keyboardhookeventargs的keyintercepted事件。這是通過一個keyboardhookeventhandler類型的方法使用以下方式來實現的:

kh.keyintercepted += new keyboardhook.keyboardhookeventhandler(kh_keyintercepted);
  這個keyboardhookeventargs返回關于被按下鍵的下列信息:

  ?? keyname:鍵名,通過把捕獲的鍵代碼強制轉換為system.windows.forms.keys而獲得。

  ?? keycode:由鍵盤鉤子返回的原來的鍵代碼

  ?? passthrough:指出是否這個keyboardhook實例被配置以允許該擊鍵傳遞到其它應用程序。如果你想允許一用戶使用alt+tab或 ctrl+esc/windows鍵切換到其它的應用程序的話,那么對之進行檢查是很有用的。

  然后,使用一個具有適當簽名的方法來執行擊鍵所調用的任何任務。下面是一個示例片斷:

void kh_keyintercepted(keyboardhookeventargs e)
{
 //檢查是否這個鍵擊事件被傳遞到其它應用程序并且停用topmost,以防他們需要調到前端
 if (e.passthrough)
 {
  this.topmost = false;
 }
 ds.draw(e.keyname);
}
  本文的剩下部分將解釋低級鍵盤鉤子是如何在keyboardhook中實現的。

  六. 實現一個低級windows api鍵盤鉤子

  在user32.dll中,windows api包含三個方法來實現此目的:

  ?? setwindowshookex,它負責建立鍵盤鉤子

  ?? unhookwindowshookex,它負責移去鍵盤鉤子

  ?? callnexthookex,它負責把擊鍵信息傳遞到下一個監聽鍵盤事件的應用程序

  創建一個能夠攔截鍵盤的應用程序的關鍵是,實現前面兩個方法,而"放棄"第三個。結果是,任何擊鍵都只能傳遞到這個應用程序中。

  為了實現這一目標,第一步是包括system.runtime.interopservices命名空間并且導入api方法,首先是setwindowshookex:

using system.runtime.interopservices
...
//在類內部:
[dllimport("user32.dll", charset = charset.auto, setlasterror = true)]
private static extern intptr setwindowshookex(int idhook,
lowlevelkeyboardproc lpfn, intptr hmod, uint dwthreadid);
  導入unhookwindowshookex和callnexthookex的代碼請見后面的討論。

  下一步是調用setwindowshookex來建立鉤子,這時需要傳遞下列四個參數:

  ?? idhook:

  這個數字決定了要建立的鉤子的類型。例如,setwindowshookex可以被用于鉤住鼠標事件(當然還有其它事件)。在本文情況下,我們僅對13有興趣,這是鍵盤鉤子的id。為了使代碼更易讀些,我們把它賦值給一個常數wh_keyboard_ll。

  ?? lpfn:

  這是一個指向函數的長指針,該函數將負責處理鍵盤事件。在c#中,"指針"是通過傳遞一個代理類型的實例而獲得的,從而使之引用一個適當的方法。這是我們在每次使用鉤子時所調用的方法。

  這里值得注意的是,這個代理實例需要被存儲于這個類的一個成員變量中。這是為了防止一旦第一個方法調用結束它會被作為垃圾回收。

  ?? hmod:

  建立鉤子的應用程序的一個實例句柄。我找到的絕大多數實例僅把它設置為intptr.zero,理由是不大可能存在該應用程序的多個實例。然而,這部分代碼使用了來自于kernel32.dll的getmodulehandle來標識準確的實例從而使這個類更具靈活性。

  ?? dwthreadid:

  當前進程的id。把它設置為0可以使這個鉤子成為全局構子,這是相應于一個低級鍵盤鉤子的正確設置。

  setwindowshookex返回一個鉤子id,這個id將被用于當應用程序結束時從鉤子鏈中脫鉤,因此它需要存儲在一個成員變量中以備將來使用。keyboardhook類中的相關代碼如下:

private hookhandlerdelegate proc;
private intptr hookid = intptr.zero;
private const int wh_keyboard_ll = 13;
public keyboardhook()
{
 proc = new hookhandlerdelegate(hookcallback);
 using (process curprocess = process.getcurrentprocess())
 using (processmodule curmodule = curprocess.mainmodule)
 {
  hookid = setwindowshookex(wh_keyboard_ll, proc,getmodulehandle(curmodule.modulename), 0);
 }
}

 

  七. 處理鍵盤事件

  如前面所提及,setwindowshookex需要一個到被用來處理鍵盤事件的回調函數的指針。它期望有一個使用如下簽名的函數:

lresult callback lowlevelkeyboardproc( int ncode,wparam wparam,lparam lparam);
  其實,建立一個函數指針的c#方法使用了一個代理,因此,向setwindowshookex指出它需要的內容的第一步是使用正確的簽名來聲明一個代理:

private delegate intptr hookhandlerdelegate(int ncode, intptr wparam, ref kbdllhookstruct lparam);
  然后,使用相同的簽名編寫一個回調方法;這個方法將包含實際上處理鍵盤事件的所有代碼。在keyboardhook的情況下,它檢查是否擊鍵應該被傳遞給其它應用程序并且接下來激發keyintercepted事件。下面是一個簡化版本的不帶有擊鍵處理代碼的情況:

private const int wm_keydown = 0x0100;
private const int wm_syskeydown = 0x0104;
private intptr hookcallback(int ncode, intptr wparam, ref kbdllhookstruct lparam)
{
 //僅為keydown事件過濾wparam,否則該代碼將再次執行-對于每一次擊鍵(也就是,相應于keydown和keyup)
 //wm_syskeydown是捕獲alt相關組合鍵所必需的
 if (ncode >= 0 && (wparam == (intptr)wm_keydown || wparam == (intptr)wm_syskeydown))
 {
  //激發事件
  onkeyintercepted(new keyboardhookeventargs(lparam.vkcode, allowkey));
  //返回一個"啞"值以捕獲擊鍵
  return (system.intptr)1;
 }
 //事件沒有被處理,把它傳遞給下一個應用程序
 return callnexthookex(hookid, ncode, wparam, ref lparam);
}
  接下來,一個到hookcallback的參考被指派給hookhandlerdelegate的一個實例并且被傳遞到setwindowshookex的調用,正如前一節所展示的。

  無論何時一個鍵盤事件發生,下列參數將被傳遞給hookcallback:

  ?? ncode:

  根據msdn文檔,回調函數應該返回callnexthookex的結果,如果這個值小于零的話。正常的鍵盤事件將返回一個大于或等于零的ncode值。

  ?? wparam:

  這個值指示發生了什么類型的事件:鍵被按下還是松開,以及是否按下的鍵是一個系統鍵(左邊或右邊的alt鍵)。

  ?? lparam:

  這是一個存儲精確擊鍵信息的結構,例如被按鍵的代碼。在keyboardhook中聲明的這個結構如下:

private struct kbdllhookstruct
{
 public int vkcode;
 int scancode;
 public int flags;
 int time;
 int dwextrainfo;
}
  其中的這兩個公共參數是在keyboardhook中的回調方法所使用的僅有的兩個參數。vkcoke返回虛擬鍵代碼,它能夠被強制轉換為system.windows.forms.keys以獲得鍵名,而flags顯示是否這是一個擴展鍵(例如,windows start鍵)或是否同時按下了alt鍵。有關于hook回調方法的完整代碼展示在每一種情況下要檢查哪些flags值。

  如果flags提供的信息和kbdllhookstruct的其它組成元素不需要,那么這個回調方法和代碼的簽名可以按如下進行修改:

private delegate intptr hookhandlerdelegate(
int ncode, intptr wparam, intptr lparam);
  在這種情況中,lparam將僅返回vkcode。

 

  八. 把擊鍵傳遞到下一個應用程序

  一個良好的鍵盤鉤子回調方法應該以調用callnexthookex函數并且返回它的結果結束。這可以確保其它應用程序能夠有機會處理針對于它們的擊鍵。

  然而,keyboardhook類的主要功能在于,阻止擊鍵被傳播到任何其它更多的應用程序。因此它無論在何時處理一次擊鍵,hookcallback都將返回一個啞值:

return (system.intptr)1;
  另一方面,它確實調用callnexthookex-如果它不處理該事件,或如果重載的構造器中的使用keyboardhook傳遞的參數允許某些組合鍵通過。

  callnexthookex被啟用-通過從user32.dll導入該函數,如下列代碼所示:

[dllimport("user32.dll", charset = charset.auto, setlasterror = true)]
private static extern intptr callnexthookex(intptr hhk, int ncode,
intptr wparam, ref keyinfostruct lparam);
  然后,被導入的方法被hookcallmethod所調用,這可以確保所有的通過鉤子接收到的參數被繼續傳遞到下一個應用程序中:

callnexthookex(hookid, ncode, wparam, ref lparam);
  如前面所提及,如果在lparam中的flags是不相關的,那么可以修改導入的callnexthookex的簽名以把lparam定義為system.intptr。

  九. 移去鉤子

  處理鉤子的最后一步是使用從user32.dll中導入的unhookwindowshookex函數移去它(當破壞keyboardhook類的實例時),如下所示:

[dllimport("user32.dll", charset = charset.auto, setlasterror = true)]
[return: marshalas(unmanagedtype.bool)]
private static extern bool unhookwindowshookex(intptr hhk);
  既然keyboardhook實現idisposable,那么這可以在dispose方法中完成。

public void dispose()

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 墨江| 古交市| 泸州市| 安达市| 江口县| 黑水县| 广饶县| 棋牌| 恩施市| 昂仁县| 时尚| 望奎县| 清水河县| 潮州市| 贵港市| 花垣县| 策勒县| 绥阳县| 双城市| 全州县| 霍州市| 中江县| 新晃| 荆州市| 安阳市| 弥渡县| 怀安县| 阳信县| 安丘市| 黑水县| 襄城县| 隆昌县| 临颍县| 德安县| 长宁区| 东乡| 德清县| 商水县| 江安县| 涿鹿县| 乐亭县|