Home > Programming > [WPF] キーボードフック(WM_KEYBOARD_LL)

[WPF] キーボードフック(WM_KEYBOARD_LL)

SetWindowsHookExは本来DLLを作成し、そのインスタンスを指定しなければならない。でも参考にしたプログラムはEXE上のフックプロセスをそのまま指定していた。何故可能なのかは不明だが確かにそのサンプルAPは動く。そこでそれをそのまま真似てみたがSetWindowsHookExからはエラーが返ってくる。この原因がわかるまで1週間近くかかってしまった。

結論から言うと(と言うか結論しか言えない)、プロジェクトのプロパティにあるデバックタグに「Visual Studioホスティングプロセスを有効にする」が規定値でオンになっている。このチェックを外さないとSetWindowsHookExはフックプロセスを異常とみなすようだ。

Jumboのブログ! | WPFを使った拡大鏡を目指して

フックってDLL作ってやらなきゃいけないと思ってけど、そんなことしなくても動くんだね。これを参考にして、キーボードのローレベルフックをやってみた。

KeyboardHook.cs

using System;
using System.Windows.Input;
using System.Runtime.InteropServices;
using System.Reflection;

using System.Windows;

namespace Sample
{
    class KeyboardHook
    {
        static int hHook = 0;
        public event KeyEventHandler keyboardEvent;

        public const int WM_KEYBOARD_LL = 13;
//      public const int WH_MOUSE_LL = 14;
        public const int WM_KEYDOWN = 0x100;
        public const int WM_KEYUP = 0x101;
        public const int WM_SYSKEYDOWN = 0x104;
        public const int WM_SYSKEYUP = 0x105;

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

        [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);

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

        public KeyboardHook()
        {
            Start();
        }

        ~KeyboardHook()
        {
            Stop();
        }

        public void Start()
        {
            if (hHook == 0)
            {
                HookProcedure = new HookProc(KeyBoardHookProcedure);

                hHook = SetWindowsHookEx(
                    WM_KEYBOARD_LL,
                    HookProcedure,
                    Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules(false)[0]),
                    0);

                if (hHook == 0)
                {
                    Stop();
                    throw new Exception("SetWindowsHookEx failed.");
                }
            }
        }

        public void Stop()
        {
            bool retMouse = true;
            if (hHook != 0)
            {
                retMouse = UnhookWindowsHookEx(hHook);
                hHook = 0;
                if (retMouse == false)
                {
                    throw new Exception("UnhookWindowsHookEx failed.");
                }
            }
        }

        protected int KeyBoardHookProcedure(int nCode, int wParam, IntPtr lParam)
        {
            if (nCode >= 0 && keyboardEvent != null)
            {
                KeyboardHookStruct KeyboardInfo =
                    (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));

                switch (wParam)
                {
                    case WM_KEYDOWN:
                    case WM_SYSKEYDOWN:
                        keyboardEvent(
                            this,
                            new KeyEventArgs(
                                Keyboard.PrimaryDevice,
                                PresentationSource.FromVisual(App.Current.MainWindow),
                                0,
                            KeyInterop.KeyFromVirtualKey(KeyboardInfo.VKCode)
                            )
                        );
                        break;
                }
            }
            return CallNextHookEx(hHook, nCode, wParam, lParam);
        }
    }
}

PresentationSource.FromVisual(App.Current.MainWindow) の部分はあやしい。そもそも、ここで要求されている InputSource の意味がよく分かっていない。たぶん、WinForm アプリでいうところの Handle みたいなものなのかなぁ…と想像しているけど。ここでは、メインウィンドウをそのまま渡している。WM_*_DOWN、WM_*_UP などなど、それぞれに event を用意してあげれば、まぁまぁ使えるものになるかもしれない。

あとはメインウィンドウあたりから

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    hook = new KeyboardHook();
    hook.keyboardEvent += new KeyEventHandler(hook_keyboardEvent);
}

void hook_keyboardEvent(object sender, KeyEventArgs e)
{
    System.Diagnostics.Debug.WriteLine(e.Key);
}

とでもしてあげれば、デバッグウィンドウにいろいろキーが表示される。

image

別にキーロガーとか作ってるわけじゃなくて、単なる興味デス。

WPF なら、根性さえあれば3Dでキーボードのどの部分がいちばん使われているかを表すヒートマップなんかも、結構簡単に作れるかもね♪

※「Visual Studioホスティングプロセスを有効にする」をOFFにするの忘れないでネ。[プロジェクト]-[*のプロパティ]メニューから、プロパティウィンドウを開き、[デバッグ]タブを開けば、チェックボックスがあるハズ。

  • 言い忘れたけど、後日の検証の結果、デバッグモードでしか動かないことを確認しています。あしからず
blog comments powered by Disqus

Home > Programming > [WPF] キーボードフック(WM_KEYBOARD_LL)

My Friend Feed

http://friendfeed.com/daruyanagi

Google Analyticator

550
 Unique Visitors 
 (1 day) 
Powered By Google Analytics

Return to page top