C# Hook鉤子執行個體代碼 截取鍵盤輸入

來源:互聯網
上載者:User

一.關於本文

以最通俗的語言說明鉤子的使用方法,具體到鉤子的詳細介紹可以參照下面的網址:

http://www.microsoft.com/china/community/program/originalarticles/techdoc/hook.mspx

二.鉤子的簡單介紹

從字面上理解,鉤子就是想鉤住些東西,在程式裡可以利用鉤子提前處理些Windows訊息。

例子:有一個Form,Form裡有個TextBox,我們想讓使用者在TextBox裡輸入的時候,不管敲鍵盤的哪個鍵,TextBox裡顯示的始終為“A”,這時我們就可以利用鉤子監聽鍵盤訊息,先往Windows的鉤子鏈表中加入一個自己寫的鉤子監聽鍵盤訊息,只要一按下鍵盤就會產生一個鍵盤訊息,我們的鉤子在這個訊息傳到TextBox之前先截獲它,讓TextBox顯示一個“A”,之後結束這個訊息,這樣TextBox得到的總是“A”。

訊息截獲順序:既然是截獲訊息,總要有先有後,鉤子是按加入到鉤子鏈表的順序以決定訊息截獲順序。就是說最後加入到鏈表的鉤子最先得到訊息。

截獲範圍:鉤子分為線程鉤子和全域鉤子,線程鉤子只能截獲本線程的訊息,全域鉤子可以截獲整個系統訊息。我認為應該盡量使用線程鉤子,全域鉤子如果使用不當可能會影響到其他程式。

三。開始


這裡就以上文提到的簡單例子做個線程鉤子。

第一步:聲明API函數

複製代碼 代碼如下:#region 第一步:聲明API函數
//使用鉤子,需要使用WindowsAPI函數,所以要先聲明這些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();

#endregion

聲明一下API函數,以後就可以直接調用了。

第二步:聲明、定義。

複製代碼 代碼如下:#region 第二步:聲明,定義委託
public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);

static int hKeyboardHook = 0;//如果hKeyboardHook不為0則說明鉤子安裝成功

HookProc KeyboardHookProcedure;
#endregion

先解釋一下委託,鉤子必須使用標準的鉤子子程,鉤子子程就是一段方法,就是處理上面例子中提到的讓TextBox顯示“A”的操作。

鉤子子程必須按照HookProc(int nCode, Int32 wParam, IntPtr lParam)這種結構定義,三個參數會得到關於訊息的資料。

當使用SetWindowsHookEx函數安裝鉤子成功後會返回鉤子子程的控制代碼,hKeyboardHook變數記錄返回的控制代碼,如果hKeyboardHook不為0則說明鉤子安裝成功。

第三步:寫鉤子子程

複製代碼 代碼如下:#region 第三步:編寫鉤子子程
//鉤子子程就是鉤子所要做的事情。

private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr IParam)
{
if (nCode >= 0)
{
textBox1.Text = "hello,fangqm.cn";
return 1;
}
return CallNextHookEx(hKeyboardHook, nCode, wParam, IParam);
}
#endregion

我們寫一個方法,返回一個int值,包括三個參數。如上面給出的代碼,符合鉤子子程的標準。

nCode參數是鉤子代碼,鉤子子程使用這個參數來確定任務,這個參數的值依賴於Hook類型。

wParam和lParam參數包含了訊息資訊,我們可以從中提取需要的資訊。

方法的內容可以根據需要編寫,我們需要TextBox顯示“ fangqm.cn”,那我們就寫在這裡。當鉤子截獲到訊息後就會調用鉤子子程,這段程式結束後才往下進行。截獲的訊息怎麼處理就要看子程的傳回值了,如果返回1,則結束訊息,這個訊息到此為止,不再傳遞。如果返回0或調用CallNextHookEx函數則訊息出了這個鉤子繼續往下傳遞,也就是傳給訊息真正的接受者。

第四步:正式啟用鉤子:安裝鉤子、卸載鉤子
準備工作都完成了,剩下的就是把鉤子裝入鉤子鏈表。
我們可以寫兩個方法在程式中合適位置調用。代碼如下:

複製代碼 代碼如下:#region 第四步:正式啟用鉤子
//鉤子安裝
public void HookStart()
{
if (hKeyboardHook == 0)//如果hKeyboardHook==0,鉤子安裝失敗
{
//建立HookProc執行個體
KeyboardHookProcedure = new HookProc(KeyboardHookProc);
//設定線程鉤子
hKeyboardHook = SetWindowsHookEx(2, KeyboardHookProc, IntPtr.Zero, GetCurrentThreadId());

if (hKeyboardHook == 0)
{
//終止鉤子
throw new Exception("安裝鉤子失敗");
}
}
}

//鉤子卸載
public void HookStop()
{
bool retKeyboard = true;
if (hKeyboardHook != 0)
{
retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
hKeyboardHook = 0;
}
if (!retKeyboard)
throw new Exception("鉤子卸載失敗");

}
#endregion

安裝鉤子和卸載鉤子關鍵就是SetWindowsHookEx和UnhookWindowsHookEx方法。
SetWindowsHookEx (int idHook, HookProc lpfn, IntPtr hInstance, int threadId) 函數將鉤子加入到鉤子鏈表中,說明一下四個參數:
idHook 鉤子類型,即確定鉤子監聽何種訊息,上面的代碼中設為2,即監聽鍵盤訊息並且是線程鉤子,如果是全域鉤子監聽鍵盤訊息應設為13,線程鉤子監聽滑鼠訊息設為7,全域鉤子監聽滑鼠訊息設為14。
lpfn 鉤子子程的地址指標。如果dwThreadId參數為0 或是一個由別的進程建立的線程的標識,lpfn必須指向DLL中的鉤子子程。 除此以外,lpfn可以指向當前進程的一段鉤子子程代碼。鉤子函數的入口地址,當鉤子鉤到任何訊息後便調用這個函數。
hInstance應用程式執行個體的控制代碼。標識包含lpfn所指的子程的DLL。如果threadId 標識當前進程建立的一個線程,而且子程代碼位於當前進程,hInstance必須為NULL。可以很簡單的設定其為本應用程式的執行個體控制代碼。
threaded 與安裝的鉤子子程相關聯的線程的標識符。如果為0,鉤子子程與所有的線程關聯,即為全域鉤子。
上面代碼中的SetWindowsHookEx方法安裝的是線程鉤子,用GetCurrentThreadId()函數得到當前的線程ID,鉤子就只監聽當前線程的鍵盤訊息。
UnhookWindowsHookEx (int idHook) 函數用來卸載鉤子,卸載鉤子與加入鉤子鏈表的順序無關,並非後進先出。

四。安裝全域鉤子

上文使用的是線程鉤子,如果要使用全域鉤子在鉤子的安裝上略有不同。如下:
SetWindowsHookEx( 13,KeyboardHookProcedure,
Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),0)
這條語句即定義全域鉤子。
子程訊息處理
鉤子子程可以得到兩個關於訊息資訊的參數wPrama、lParam。怎麼將這兩個參數轉成我們更容易理解的訊息呢。
對於滑鼠訊息,我們可以定義下面這個結構:

複製代碼 代碼如下:public struct MSG
{
public Point p;
public IntPtr HWnd;
public uint wHitTestCode;
public int dwExtraInfo;
}

對於鍵盤訊息,我們可以定義下面這個結構:

複製代碼 代碼如下:public struct KeyMSG
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}

然後我們可以在子程裡用下面語句將lParam資料轉換成MSG或KeyMSG結構資料
MSG m = (MSG) Marshal.PtrToStructure(lParam, typeof(MSG));
KeyMSG m = (KeyMSG) Marshal.PtrToStructure(lParam, typeof(KeyMSG));

這樣可以更方便的得到滑鼠訊息或鍵盤訊息的相關資訊,例如p即為滑鼠座標,HWnd即為滑鼠點擊的控制項的控制代碼,vkCode即為按鍵代碼。
註:這條語句對於監聽滑鼠訊息的線程鉤子和全域鉤子都可以使用,但對監聽鍵盤訊息的線程鉤子使用會出錯,目前在找原因。
如果是監聽鍵盤訊息的線程鉤子,我們可以根據lParam值的正負確定按鍵是按下還是抬起,根據wParam值確定是按下哪個鍵。

複製代碼 代碼如下:// 按下的鍵
Keys keyData = (Keys)wParam;
if(lParam.ToInt32() > 0)
{
// 鍵盤按下
}
if(lParam.ToInt32() < 0)
{
// 鍵盤抬起
}

如果是監聽鍵盤訊息的全域鉤子,按鍵是按下還是抬起要根據wParam值確定。
wParam = = 0x100 // 鍵盤按下
wParam = = 0x101 // 鍵盤抬起

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.