C# – Chặn/vô hiệu hóa thông điệp keyboard bằng Hook

Như trong bài Giới thiệu kĩ thuật Hook và các khái niệm cơ bản đã trình bày, bạn có thể thấy rằng việc gọi CallNextHookEx() là việc cần thiết để tiếp tục gọi các hook procedure khác trong hook chain. Trường hợp bạn muốn chặn một thông điệp hay sự kiện nào đó, chỉ đơn giản là không gọi hàm CallNextHookEx() và trả về giá trị 1 trong hook procedure.

Trong ví dụ sau tôi sẽ chặn các phím Left Windows trên bàn phím, đồng thời cho Sendkey tên của phím đó để thay thế. Khi test bạn hãy mở một cửa sổ soạn thảo văn bản ra (như notepad) và focus nó. Sau đó hãy nhấn Left Windows, bạn sẽ không thấy Start Menu hiện ra và cửa sổ notepad sẽ được chèn vào dòng chữ LWin.

Tôi lấy lại ví dụ trong bài trước và sửa lại Hook procedure:

private IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode >= 0)
    {
        KeyboardHookStruct kbStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));

        if (wParam == (IntPtr)WM_KEYDOWN)
        {
            Keys k= (Keys)kbStruct.VirtualKeyCode;

            // disable Left Windows
            if(k==Keys.LWin)
            {
                SendKeys.Send(k.ToString());
                return (IntPtr)1;
            }else if(k==Keys.Escape) // exit program
                Application.Exit();
        }
    }

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

Để làm ví dụ trên bạn tạo một WindowsFormsApplication mới, xóa Form1 và thêm lớp Y2KeyboardHook sau:

using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Windows.Forms;
using System.Reflection;
using System.ComponentModel;

namespace HookApp
{

    class Y2KeyboardHook
    {

        #region Win32 API Functions and Constants

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook,
            KeyboardHookDelegate lpfn, IntPtr hMod, int dwThreadId);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
            IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        private const int WH_KEYBOARD_LL = 13;

        private const int WM_KEYDOWN = 0x0100;
        private const int WM_KEYUP = 0x101;

        #endregion

        private KeyboardHookDelegate _hookProc;
        private IntPtr _hookHandle = IntPtr.Zero;

        public delegate IntPtr KeyboardHookDelegate(int nCode, IntPtr wParam, IntPtr lParam);

        [StructLayout(LayoutKind.Sequential)]
        public struct KeyboardHookStruct
        {
            public int VirtualKeyCode;
            public int ScanCode;
            public int Flags;
            public int Time;
            public int ExtraInfo;
        }

        // destructor
        ~Y2KeyboardHook()
        {
            Uninstall();
        }

        public void Install()
        {
            _hookProc = KeyboardHookProc;
            _hookHandle = SetupHook(_hookProc);

            if (_hookHandle == IntPtr.Zero)
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        private IntPtr SetupHook(KeyboardHookDelegate hookProc)
        {
            IntPtr hInstance = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]);

            return SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);
        }

        private IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                KeyboardHookStruct kbStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));

                if (wParam == (IntPtr)WM_KEYDOWN)
                {
                    Keys k= (Keys)kbStruct.VirtualKeyCode;

                    // disable Left Windows, Left Shift and Left Control keys
                    if(k==Keys.LWin)
                    {
                        SendKeys.Send(k.ToString());
                        return (IntPtr)1;
                    }else if(k==(Keys.Escape)) // exit program
                        Application.Exit();
                }
            }

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

        public void Uninstall()
        {
            UnhookWindowsHookEx(_hookHandle);
        }

    }
}

Lớp Program:

using System;
using System.Windows.Forms;
using HookApp;

namespace WindowsFormsApplication1
{
    internal sealed class Program
    {

        [STAThread]
        private static void Main(string[] args)
        {
            Y2KeyboardHook keyboardHook = new Y2KeyboardHook();
            keyboardHook.Install();
            Application.Run();
        }

    }
}

Muốn thoát ứng dụng bạn hãy nhấn Escape.

.NET FAQ

Advertisements

31 thoughts on “C# – Chặn/vô hiệu hóa thông điệp keyboard bằng Hook

  1. Cám ơn miình đã chặn đc các phím trên keyboard rồi, cái quan trọng hơn là mình muốn khi ấn vào Left Win thì program sẽ “ấn” hộ mình 1 phím nào đó. Ý mình muốn hỏi là có cách nào “ấn phím” ngoài cách sử dụng SendKeys.Send hay ko ? Cách xài SendKeys.Send ko đáp ứng vấn đề mình đang khắc phục 😦

    Phản hồi
    • Các sự kiện nhấn phím sẽ được hệ thống gửi thông điệp đến cửa sổ đang active, vì vậy bạn dùng SendKeys cũng tương tự. Tuy nhiên nếu muốn gửi thông điệp đến một cửa sổ nào đó thì bạn có thể dùng API SendMessage().

      Khai báo:

      [DllImport(“user32.dll”, CharSet = CharSet.Auto)]
      static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

      Đối với việc gửi phím:
      – hWnd là handle của cửa sổ cần gửi thông điệp.
      – Msg là mã thông điệp. Ví dụ WM_KEYDOWN.
      – wParam: VirtualKeyCode
      – lParam: scanCode của phím

      Bạn có thể xem khai báo của hàm này trong C# tại link sau:

      SendMessage (user32)

      Phản hồi
      • Vậy mình muốn hook combination keys ví dụ Alt + 1, Alt + 2 thì thế nào hả bạn ?
        Mình thử
        if (Keys.Alt == Control.ModifierKeys && k == Keys.Numpad1)
        {
        SendKeys.Send(k.ToString());
        return (IntPtr)1;
        }

        mà ko đc 😦

      • Để kết hợp các giá trị của enum của Keys lại, bạn dùng toán tử bitwise OR ‘|’. Ví dụ:

        k==(Keys.Control | Keys.NumPad1)

        Tuy nhiên do enum Keys có quá nhiều giá trị nên chỉ kết hợp được một số phím với Shift, Control, Alt.
        Bạn có thể xem FlagAttribute trên MSDN để tham khảo.

      • Khi mình sử dung
        if (Keys.Control == Control.ModifierKeys && k == Keys.NumPad1) thì chạy ổn cả

        Nhưng khi mình đổi sang
        if (Keys.Alt == Control.ModifierKeys && k == Keys.Numpad1) thì ko đc 😦

        Chả hiểu Alt với Control khác nhau ở điểm j…

  2. Mình đang làm 1 project có sử dụng đoạn code này của bạn. Trong giai đoạn phát triển mình gặp 1 lỗi mà mình ko sao khắc phục được. Mình làm 1 soft sử dụng hook, hook sẽ bắt đầu đc install khi 1 program khác (ko phải soft chính) đc focus, soft mình viết sẽ chạy background và chỉ install hook khi program kia đang là active window. Hook chỉ bắt đc 1 hoặc 2 phím đầu tiên, 1 lúc sau soft báo lỗi CallBackonCollectedDelegate ở đoạn code


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

    trong method private IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)

    Nguyên văn:
    “A callback was made on a garbage collected delegate of type ‘key preview!Utilities.globalKeyboardHook+keyboardHookProc::Invoke’. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.”

    Mình search rất lâu trên mạng, tìm được khá nhiều chỉ dẫn nhưng vẫn chưa khắc phục đc lỗi này. Khi mình set lại cho hook install trên toàn máy thì ko thấy lỗi này nữa, tất cả đều chạy ổn định (trong program mình cần focus cũng chạy ổn). Nhưng cái mình muốn là hook chỉ chạy đc khi active windows là program nói trên thôi chứ ko phải trên toàn máy vì sẽ rất bất tiện nếu viết trên Word hoặc chat…
    Rất mong ý kiển của bạn.

    Phản hồi
  3. Mình thêm static vào

    private static KeyboardHookDelegate _hookProc;

    thì program gặp lỗi ngay khi bắt phím đầu tiên 😦

    Còn nếu để yên private KeyboardHookDelegate _hookProc; thì program bắt đến phím thứ 3 – 10 mới bắt đầu báo lỗi.

    Mình search trên net thấy 1 trong những chỉ dẫn là thêm method GC.KeepAlive(“your Delegate instance”).nhưng chẳng biết phải thêm vào đâu…

    Phản hồi
  4. Cửa sổ soạn thảo của WordPress có hai chế độ là trực quan và source code. Hai nút này nằm ở góc phải phía trên khung soạn thảo. Bạn nhấn vào nút xem sourcecode để nhập mã vào vị trí cần chèn code, rồi cho code C# vào bên trong hai thẻ đó. Xem trong link mình post ở comment trước để làm ví dụ.

    [sourcecode language=”css”]
    your code here
    [/sourcecode]

    Phản hồi
  5. Chào bạn. Mình Dùng if (k == Keys.Alt | k == Keys.F4)
    {
    SendKeys.Send(null);
    return (IntPtr)1;
    }
    Nhưng sao nó không hoạt động vậy bạn.những phím khác thì bình thường nhưng trừ các phim Alt,Control,Tab,Shift thì không hoạt động. Tại sao vậy bạn ! Thanks

    Phản hồi
  6. Pingback: Giới thiệu kĩ thuật Hook và các khái niệm cơ bản | NGỌC TẤN KIẾN THỨC LẬP TRÌNH AN GIANG

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s