グローバルフック

軽く調べたところグローバルフックはDLLを使わなければ実装できないという情報がほとんどだった。しかし疑り深い私は海外の情報も含めてかなり調べてみた。すると低レベルのキーボードフック、マウスフックは何故かDLLを使わなくても実装できるという情報を見つけた。具体的なコードは以下。

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

namespace garu.Testhook
{
    static class TestHook
    {
        [STAThread]
        static void Main(string[] args)
        {
            Application.Run(new MainForm());
        }
    }

    class MainForm : Form
    {
        //
        // DELEGATE for Hook
        //
        public delegate int HookHandler(
            int code, WM message, IntPtr state);

        //
        // SetWindowsHookEx
        //
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr SetWindowsHookEx(
            WH hookType, HookHandler hookDelegate, IntPtr module, uint threadId);

        //
        // HookType
        //
        public enum WH
        {
            KEYBOARD_LL = 13,
            MOUSE_LL = 14,
        }

        //
        // UnhookWindowsHookEx
        //
        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool UnhookWindowsHookEx(IntPtr hook);

        //
        // CallNextHookEx
        //
        [DllImport("user32.dll")]
        public static extern int CallNextHookEx(
            IntPtr hook, int code, WM message, IntPtr state);

        //
        // Win32 Message
        //
        public enum WM
        {
            KEYDOWN = 0x0100,
            KEYUP = 0x0101,
            SYSKEYDOWN = 0x0104,
            SYSKEYUP = 0x0105,
            MOUSEMOVE = 0x0200,
            LBUTTONDOWN = 0x0201,
            LBUTTONUP = 0x0202,
            LBUTTONDBLCLK = 0x0203,
            RBUTTONDOWN = 0x0204,
            RBUTTONUP = 0x0205,
            RBUTTONDBLCLK = 0x0206,
            MBUTTONDOWN = 0x0207,
            MBUTTONUP = 0x0208,
            MBUTTONDBLCLK = 0x0209,
            MOUSEWHEEL = 0x020A,
            XBUTTONDOWN = 0x020B,
            XBUTTONUP = 0x020C,
            XBUTTONDBLCLK = 0x020D,
            MOUSEHWHEEL = 0x020E,
        }

        //
        // (private) Field 
        //
        HookHandler hookDelegate;
        IntPtr hook;

        public MainForm()
        {
            WH hookType = WH.KEYBOARD_LL;
            hookDelegate = new HookHandler(OnHook);
            IntPtr hMod = Marshal.GetHINSTANCE(
                System.Reflection.Assembly.
                GetExecutingAssembly().GetModules()[0]);
            hook = SetWindowsHookEx(hookType, hookDelegate, hMod, 0);
            if (hook == IntPtr.Zero)
            {
                int errorCode = Marshal.GetLastWin32Error();
                throw new Win32Exception(errorCode);
            }
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);
            if (hook != IntPtr.Zero)
            {
                UnhookWindowsHookEx(hook);
            }
        }

        int OnHook(int code, WM message, IntPtr state)
        {
            Console.WriteLine("message={0}", message);
            return CallNextHookEx(hook, code, message, state);
        }
    }
}

確かに別アプリをアクティブにしてもキーイベントは拾っている。
Formアプリで作成しているが、デバッグをConsoleへ吐き出しているのでデバッグ環境で「表示→出力」として出力ウインドウを表示させればメッセージが確認できる。

重要な注意点が一つ。デバッグ環境で実行する際にはプロジェクトのプロパティのデバッグタブで「Visual Studio ホスティングプロセスを有効にする」のチェックを外さないとHookに失敗する。詳細については今回の目的の範囲外なので割愛する。