【C#】グローバルキーフックを行う【忘備録】

スポンサーリンク
スポンサーリンク

はじめに

グローバルキーフックのためのクラスを作成したので、忘備録として残しておきます。

インスタンスを生成し「KeyBoardHook」を呼び出してから「KeyBoardUnHook」を呼び出すまでグローバルキーフックの検出を行います。

グローバルキーフックを検出した後、独自イベント「KeyHookDownEvent」(キー押下時)と「KeyHookUpEvent」(キー離上時)にイベントを発生させます。

コード

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
    
/// <summary>
/// グローバルキーフックイベントを実装するクラス
/// </summary>
public class Common_KeyBoardHook
{
    #region Const
    private protected const int WH_KEYBOARD_LL = 0x000D;
    private protected const int HC_ACTION = 0;
    private protected const int WM_KEYDOWN = 0x0100;
    private protected const int WM_KEYUP = 0x0101;
    private protected const int WM_SYSKEYDOWN = 0x0104;
    private protected const int WM_SYSKEYUP = 0x0105;
    [Flags]
    private enum KBDLLHOOKSTRUCTFlags : uint
    {
        LLKHF_EXTENDED = 0x01,
        LLKHF_INJECTED = 0x10,
        LLKHF_ALTDOWN = 0x20,
        LLKHF_UP = 0x80
    }
    [StructLayout(LayoutKind.Sequential)]
    private class KBDLLHOOKSTRUCT
    {
        public uint vkCode;
        public uint scanCode;
        public KBDLLHOOKSTRUCTFlags flags;
        public uint time;
        public UIntPtr dwExtraInfo;
    }
    public enum HookKeyCode : int
    {
        VK_LBUTTON = 0x01, VK_RBUTTON, VK_CANCEL, VK_MBUTTON, VK_XBUTTON1, VK_XBUTTON2,
        VK_BACK = 0x08, VK_TAB,
        VK_CLEAR = 0x0C, VK_RETURN,
        VK_SHIFT = 0x10, VK_CONTROL, VK_MENU, VK_PAUSE, VK_CAPITAL, VK_KANA,
        VK_JUNJA = 0x17, VK_FINAL, VK_KANJI,
        VK_ESCAPE = 0x1B, VK_CONVERT, VK_NONCONVERT, VK_ACCEPT, VK_MODECHANGE,
        VK_SPACE, VK_PRIOR, VK_NEXT, VK_END, VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN,
        VK_SELECT, VK_PRINT, VK_EXECUTE, VK_SNAPSHOT, VK_INSERT, VK_DELETE, VK_HELP,
        VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9,
        VK_A = 0x41, VK_B, VK_C, VK_D, VK_E, VK_F, VK_G, VK_H,
        VK_I, VK_J, VK_K, VK_L, VK_M, VK_N, VK_O, VK_P, VK_Q, 
        VK_R, VK_S, VK_T, VK_U, VK_V, VK_W, VK_X, VK_Y, VK_Z,
        VK_LWIN, VK_RWIN, VK_APPS,
        VK_SLEEP = 0x5F, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4,
        VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9,
        VK_MULTIPLY, VK_ADD, VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL, VK_DIVIDE,
        VK_F1, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8,
        VK_F9, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16,
        VK_F17, VK_F18, VK_F19, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24,
        VK_NUMLOCK = 0x90, VK_SCROLL,
        VK_LSHIFT = 0xA0, VK_RSHIFT, VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU,
        VK_BROWSER_BACK, VK_BROWSER_FORWARD, VK_BROWSER_REFRESH, VK_BROWSER_STOP,
        VK_BROWSER_SEARCH, VK_BROWSER_FAVORITES, VK_BROWSER_HOME,
        VK_VOLUME_MUTE, VK_VOLUME_DOWN, VK_VOLUME_UP,
        VK_MEDIA_NEXT_TRACK, VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_MEDIA_PLAY_PAUSE,
        VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LAUNCH_APP1, VK_LAUNCH_APP2,
        VK_OEM_1 = 0xBA, VK_OEM_PLUS, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_2, VK_OEM_3,
        VK_OEM_4 = 0xDB, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8,
        VK_OEM_102 = 0xE2,
        VK_PROCESSKEY = 0xE5,
        VK_PACKET = 0xE7,
        VK_ATTN = 0xF6, VK_CRSEL, VK_EXSEL, VK_EREOF,
        VK_PLAY, VK_ZOOM, VK_NONAME, VK_PA1, VK_OEM_CLEAR
    }
    #endregion

    #region Private
    private delegate IntPtr KeyBoardHookProc(int nCode, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook, KeyBoardHookProc lpfn, IntPtr hMod, uint dwThreadId);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);
    private IntPtr HookPtr = IntPtr.Zero;
    private KeyBoardHookProc? HookProc;
    private protected void OnKeyHookDownEvent(int KeyCode)
    {
        KeyHookDownEvent?.Invoke(this, new(KeyCode));
    }
    private protected void OnKeyHookUpEvent(int KeyCode)
    {
        KeyHookUpEvent?.Invoke(this, new(KeyCode));
    }
    private IntPtr HookCallBack(int nCode, IntPtr wParam, IntPtr lParam)
    {
        IntPtr NextHookPtr = CallNextHookEx(HookPtr, nCode, wParam, lParam);

        //例外処理
        if (nCode < HC_ACTION)
        {
            return NextHookPtr;
        }

        //キーコードの取得
        object? PtrStructure = Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
        if (PtrStructure == null)
        {
            return NextHookPtr;
        }
        KBDLLHOOKSTRUCT KeyHookStruct = (KBDLLHOOKSTRUCT)PtrStructure;
        int KeyCode = (int)KeyHookStruct.vkCode;

        //キーダウンイベント
        if (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)
        {
            OnKeyHookDownEvent(KeyCode);
        }
        //キーアップイベント
        else if (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP)
        {
            OnKeyHookUpEvent(KeyCode);
        }

        return NextHookPtr;
    }
    #endregion

    #region Public
    /// <summary>
    /// キーボードフックを設定する
    /// </summary>
    /// <returns>設定の是非(T/F)</returns>
    public bool KeyBoardHook()
    {
        Process CurrentProcess = Process.GetCurrentProcess();
        ProcessModule? CurrentModule = CurrentProcess.MainModule;
        if (CurrentModule == null)
        {
            return false;
        }

        string? NameModule = CurrentModule.ModuleName;
        if (NameModule == null)
        {
            return false;
        }

        HookProc = HookCallBack;
        HookPtr = SetWindowsHookEx(WH_KEYBOARD_LL, HookProc, GetModuleHandle(NameModule), 0);

        return true;
    }

    /// <summary>
    /// キーボードフックを解除する
    /// </summary>
    /// <returns>解除の是非</returns>
    public bool KeyBoardUnHook()
    {
        bool RetUnhook = UnhookWindowsHookEx(HookPtr);
        HookPtr = IntPtr.Zero;

        return RetUnhook;
    }

    /// <summary>
    /// キーボードフックイベント
    /// </summary>
    /// <param name="sender">KBDLLHOOKSTRUCT構造へのポインタ</param>
    /// <param name="e">キーフックイベント</param>
    public delegate void KeyHookEventHandler(object sender, KeyHookEventArgs e);
    public event KeyHookEventHandler? KeyHookDownEvent;
    public event KeyHookEventHandler? KeyHookUpEvent;
    #endregion
}

/// <summary>
/// キーフックイベント
/// </summary>
public class KeyHookEventArgs : EventArgs
{
    public int KeyCode { get; }
    public KeyHookEventArgs(int InputKeyCode)
    {
        KeyCode = InputKeyCode;
    }
}

使い方

参考程度に。

//WPF OnStartUp等の開始処理内に記載
KeyBoardHook.KeyHookDownEvent += KeyHookDown;
KeyBoardHook.KeyHookUpEvent += KeyHookUp;
_ = KeyBoardHook.KeyBoardHook();
void KeyHookDown(object sender, KeyHookEventArgs e){//キー押下時の処理}
void KeyHookUp(object sender, KeyHookEventArgs e){//キー離上時の処理}
//WPF OnExit等の終了処理内に記載
_ = KeyBoardHook.KeyBoardUnHook();

コメント

タイトルとURLをコピーしました