[C#菜鳥]C# Hook (一)


轉過來的文章,出處已經不知道了,但只這篇步驟比較清晰,就貼出來了。

一。寫在最前

本文的內容只想以最通俗的語言說明鈎子的使用方法,具體到鈎子的詳細介紹可以參照下面的網址:

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函數

使用鈎子,需要使用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();

 

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

第二步:聲明、定義。

 

public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);

static int hKeyboardHook = 0;

HookProc KeyboardHookProcedure;

 

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

鈎子回調必須按照 HookProc(int nCode, Int32 wParam, IntPtr lParam) 這種結構定義,三個參數會得到關於消息的數據。

當使用SetWindowsHookEx函數安裝鈎子成功后會返回鈎子回調的句柄,hKeyboardHook變量記錄返回的句柄,如果hKeyboardHook不為0則說明鈎子安裝成功。

第三步:寫鈎子回調

鈎子回調就是鈎子所要做的事情。

private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
    if (nCode >= 0)
    {
        textbox1.Text = "A";
        return 1;
    }

    return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}

 

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

nCode參數是鈎子句柄代碼,鈎子回調使用這個參數來確定任務,這個參數的值依賴於Hook類型。

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

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

 

第四步:安裝鈎子、卸載鈎子

准備工作都完成了,剩下的就是把鈎子裝入鈎子鏈表。

我們可以寫兩個方法在程序中合適的位置調用。代碼如下:

 

// 安裝鈎子
public void HookStart()
{
    if (hMouseHook == 0)
    {
        // 創建HookProc實例
        MouseHookProcedure = new HookProc(MouseHookProc);

        // 設置線程鈎子
        hMouseHook = SetWindowsHookEx(2, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());

        // 如果設置鈎子失敗
        if (hMouseHook == 0)
        {
            HookStop();
            throw new Exception("SetWindowsHookEx failed.");
        }
    }
}

// 卸載鈎子
public void HookStop()
{
    bool retKeyboard = true;

    if (hKeyboardHook != 0)
    {
        retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
        hKeyboardHook = 0;
    }

    if (!(retMouse && retKeyboard)) throw new Exception("UnhookWindowsHookEx failed.");    
}

 

安裝鈎子和卸載鈎子關鍵就是SetWindowsHookEx和UnhookWindowsHookEx方法。

 SetWindowsHookEx (int idHook, HookProc lpfn, IntPtr hInstance, int threadId)  函數將鈎子加入到鈎子鏈表中,說明一下四個參數:

idHook 鈎子類型,即確定鈎子監聽哪種消息, 可以監視窗口過程,也監視消息隊列。上面的代碼中設為2,即監聽鍵盤消息並且是線程鈎子,如果是全局鈎子監聽鍵盤消息應設為13,線程鈎子監聽鼠標消息設為7,全局鈎子監聽鼠標消息設為14。

代碼為5,即C++中的WH_CBT (WH_CBT 當Windows激活、產生、釋放(關閉)、最小化、最大化或改變窗口時都將觸發此事件)

lpfn 鈎子回調的地址指針。根據鈎子類型,設置不同的回調函數如果threadId參數為0 或是一個由別的進程創建的線程的標識,lpfn必須指向DLL中的鈎子子程。 除此以外,lpfn可以指向當前進程的一段鈎子回調代碼。鈎子函數的入口地址,當鈎子鈎到任何消息后立刻調用這個函數。

hInstance 應用程序(dll)實例的句柄。標識包含lpfn所指的回調的DLL。如果threadId 表示當前進程創建的一個線程,而且子程代碼位於當前進程,hInstance必須為NULL(即線程鈎子傳null)。

threadId 設置鈎子的線程ID,如果為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 // 鍵盤抬起

 

 

 

完整代碼:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace HookWndProc
{
    public partial class Form1 : Form
    {
        // 安裝鈎子
        [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 delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);


        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            HookStart();
        }

        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {

            if (nCode >= 0 && wParam == WM_KEYDOWN)
            {
                int vkCode = Marshal.ReadInt32(lParam);  //按鍵ascii碼

                if (vkCode.ToString() == "13")
                {
                    Console.WriteLine("按了Enter");
                }

                //返回1 相當於屏蔽了Enter
                return 1;
            }


            return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
        }


        private int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                switch (wParam)
                {
                    case WM_LBUTTONDOWN:
                        Console.WriteLine("鼠標左鍵按下");
                        break;
                    case WM_LBUTTONUP:
                        Console.WriteLine("鼠標左鍵抬起");
                        break;
                    case WM_LBUTTONDBLCLK:
                        Console.WriteLine("鼠標左鍵雙擊");
                        break;
                    case WM_RBUTTONDOWN:
                        Console.WriteLine("鼠標右鍵按下");
                        break;
                    case WM_RBUTTONUP:
                        Console.WriteLine("鼠標右鍵抬起");
                        break;
                    case WM_RBUTTONDBLCLK:
                        Console.WriteLine("鼠標右鍵雙擊");
                        break;
                }
            }

            return CallNextHookEx(hMouseHook, nCode, wParam, lParam);

        }


        static int hMouseHook = 0;
        HookProc MouseHookProcedure;

        static int hKeyboardHook = 0;
        HookProc KeyboardHookProcedure;       

        // 安裝鈎子
        public void HookStart()
        {
            IntPtr hInstance = LoadLibrary("User32");

            if (hKeyboardHook == 0)
            {
                // 創建HookProc實例
                KeyboardHookProcedure = new HookProc(KeyboardHookProc);

                // 設置鈎子
                hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, hInstance, 0);

                // 如果設置鈎子失敗
                if (hKeyboardHook == 0)
                {
                    HookStop();
                    throw new Exception("SetWindowsHookEx failed.");
                }
            }

            if (hMouseHook == 0)
            {
                MouseHookProcedure = new HookProc(MouseHookProc);
                hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure, hInstance, 0);

                // 如果設置鈎子失敗
                if (hMouseHook == 0)
                {
                    HookStop();
                    throw new Exception("SetWindowsHookEx failed.");
                }
            }
        }


        // 卸載鈎子
        public void HookStop()
        {
            bool retKeyboard = true;

            bool retMouse = true;

            if (hKeyboardHook != 0)
            {
                retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
                hKeyboardHook = 0;
            }

            if (hMouseHook != 0)
            {
                retMouse = UnhookWindowsHookEx(hMouseHook);
                hMouseHook = 0;
            }


            if (!(retMouse && retKeyboard)) throw new Exception("UnhookWindowsHookEx failed.");
        }

       

        #region 鈎子類型的枚舉
        public const int WH_JOURNALRECORD = 0;      //監視和記錄輸入事件。安裝一個掛鈎處理過程,對寄送至系統消息隊列的輸入消息進行紀錄
        public const int WH_JOURNALPLAYBACK = 1;    //回放用WH_JOURNALRECORD記錄事件
        public const int WH_KEYBOARD = 2;           //鍵盤鈎子,鍵盤觸發消息。WM_KEYUP或WM_KEYDOWN消息
        public const int WH_GETMESSAGE = 3;         //發送到窗口的消息。GetMessage或PeekMessage觸發
        public const int WH_CALLWNDPROC = 4;        //發送到窗口的消息。由SendMessage觸發
        public const int WH_CBT = 5;                //當基於計算機的訓練(CBT)事件發生時 
        public const int WH_SYSMSGFILTER = 6;       //同WH_MSGFILTER一樣,系統范圍的。 
        public const int WH_MOUSE = 7;              //鼠標鈎子,查詢鼠標事件消息
        public const int WH_HARDWARE = 8;           //非鼠標、鍵盤消息時
        public const int WH_DEBUG = 9;              //調試鈎子,用來給鈎子函數除錯
        public const int WH_SHELL = 10;             //外殼鈎子,當關於WINDOWS外殼事件發生時觸發. 
        public const int WH_FOREGROUNDIDLE = 11;    //前台應用程序線程變成空閑時候,鈎子激活。
        public const int WH_CALLWNDPROCRET = 12;    //發送到窗口的消息。由SendMessage處理完成返回時觸發
        public const int WH_KEYBOARD_LL = 13;       //此掛鈎只能在Windows NT中被安裝,用來對底層的鍵盤輸入事件進行監視
        public const int WH_MOUSE_LL = 14;          //此掛鈎只能在Windows NT中被安裝,用來對底層的鼠標輸入事件進行監視


        public const int WM_MOUSEMOVE = 0x200;
        public const int WM_LBUTTONDOWN = 0x201;
        public const int WM_RBUTTONDOWN = 0x204;
        public const int WM_MBUTTONDOWN = 0x207;
        public const int WM_LBUTTONUP = 0x202;
        public const int WM_RBUTTONUP = 0x205;
        public const int WM_MBUTTONUP = 0x208;
        public const int WM_LBUTTONDBLCLK = 0x203;
        public const int WM_RBUTTONDBLCLK = 0x206;
        public const int WM_MBUTTONDBLCLK = 0x209;


        public const int WM_KEYDOWN = 256;

        #endregion


        [DllImport("kernel32.dll")]
        static extern IntPtr LoadLibrary(string lpFileName);

      
    }
}

 

 

 

 

引用

Hook鈎子C#實例

常見注入手法第四講,SetWindowsHookEx全局鈎子注入.以及注入QQ32位實戰.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM