- 2009-03-29 (日) 18:55
- Programming
SetWindowsHookExは本来DLLを作成し、そのインスタンスを指定しなければならない。でも参考にしたプログラムはEXE上のフックプロセスをそのまま指定していた。何故可能なのかは不明だが確かにそのサンプルAPは動く。そこでそれをそのまま真似てみたがSetWindowsHookExからはエラーが返ってくる。この原因がわかるまで1週間近くかかってしまった。
結論から言うと(と言うか結論しか言えない)、プロジェクトのプロパティにあるデバックタグに「Visual Studioホスティングプロセスを有効にする」が規定値でオンになっている。このチェックを外さないとSetWindowsHookExはフックプロセスを異常とみなすようだ。
フックって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);
}
とでもしてあげれば、デバッグウィンドウにいろいろキーが表示される。
別にキーロガーとか作ってるわけじゃなくて、単なる興味デス。
WPF なら、根性さえあれば3Dでキーボードのどの部分がいちばん使われているかを表すヒートマップなんかも、結構簡単に作れるかもね♪
※「Visual Studioホスティングプロセスを有効にする」をOFFにするの忘れないでネ。[プロジェクト]-[*のプロパティ]メニューから、プロパティウィンドウを開き、[デバッグ]タブを開けば、チェックボックスがあるハズ。
- Newer: [WPF] アプリケーションの実行パスを取得する
- Older: キャンプの予定はお流れ。
-
daruyanagi

