上一篇的鍵盤掛鉤範例所 設置的 hook 是 WH_KEYBOARD,其鍵盤攔截的有效範圍僅限於該應用程式。如果你需要無時無刻攔截到特定按鍵(不管目前作用中的視窗是不是你的應用程式都要攔 到),則必須設置全域掛鉤(global hook),其對應的 hook 常數是 WH_KEYBOARD_LL(數值為 13,最後的 LL 是 Low Level 的縮寫)。

以下是範例原始碼:

    1 using System;

    2 using System.ComponentModel;

    3 using System.Windows.Forms;

    4 using System.Diagnostics;

    5 using System.Runtime.InteropServices;

    6 

    7 namespace KeyboardHook

    8 {

    9     public partial class Form1 : Form

   10     {

   11         public Form1()

   12         {

   13             InitializeComponent();

   14         }

   15 

   16         const int WH_KEYBOARD_LL = 13;

   17 

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

   19 

   20         private static int m_HookHandle = 0;    // Hook handle

   21         private HookProc m_KbdHookProc;            // 鍵盤掛鉤函式指標

   22 

   23         [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]

   24         private static extern IntPtr GetModuleHandle(string lpModuleName);

   25 

   26         // 設置掛鉤.

   27         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

   28         public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

   29 

   30         // 將之前設置的掛鉤移除。記得在應用程式結束前呼叫此函式.

   31         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

   32         public static extern bool UnhookWindowsHookEx(int idHook);

   33 

   34         // 呼叫下一個掛鉤處理常式(若不這麼做,會令其他掛鉤處理常式失效).

   35         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

   36         public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);

   37 

   38         private void button2_Click(object sender, EventArgs e)

   39         {

   40             if (m_HookHandle == 0)

   41             {

   42                 using (Process curProcess = Process.GetCurrentProcess())

   43                 using (ProcessModule curModule = curProcess.MainModule)

   44                 {

   45                     m_KbdHookProc = new HookProc(Form1.KeyboardHookProc);

   46 

   47                     m_HookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, m_KbdHookProc,

   48                         GetModuleHandle(curModule.ModuleName), 0);

   49                 }

   50 

   51                 if (m_HookHandle == 0)

   52                 {

   53                     MessageBox.Show("呼叫 SetWindowsHookEx 失敗!");

   54                     return;

   55                 }

   56                 button2.Text = "解除低階鍵盤掛鉤";

   57             }

   58             else

   59             {

   60                 bool ret = UnhookWindowsHookEx(m_HookHandle);

   61                 if (ret == false)

   62                 {

   63                     MessageBox.Show("呼叫 UnhookWindowsHookEx 失敗!");

   64                     return;

   65                 }

   66                 m_HookHandle = 0;

   67                 button2.Text = "設置低階鍵盤掛鉤";

   68             }

   69         }

   70 

   71         public static int KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)

   72         {

   73             // 跟上一個範例相同,略。

  105         }

  106 

  107     }

  108 }

NOTE:

  • 執行此範例時,按下按鈕完成鍵盤掛鉤的設置後,可任意切換到別的應用程式。在任何地方按下的 Ctrl、Alt、Shift、和 F8 按鍵都會被攔到,你可以在 Visual Studio 的 Output 視窗中觀察攔截到按鍵時的輸出訊息。
  • 全域的(低階)Windows 掛鉤通常必須實作成 Win32 DLL(.NET managed DLL 不行),但有兩個例外: WH_KEYBOARD_LL 和 WH_MOUSE_LL。也幸虧有這兩個特例,我們才能夠方便地在 .NET Windows Forms 應用程式中攔截全域的鍵盤事件。
  • 注意 42~48 行,在設置全域鍵盤掛鉤,這裡傳入的是 module handle,而不像上一個範例傳入執行緒 ID。