iTunesをグローバルに制御するためにキーの押下を監視するプログラムを作成しています。また、いくつかの WinForms (トラック情報を表示し、オプションを編集するため) もあります。
低レベルのキーボード フックは、しばらくの間はうまく機能します。プログラムを起動すると、キーボード フックが設定され、iTunes が開きます。次に、メモ帳を開くと、大量の情報を非常に高速に入力でき、すべてのストロークがキャプチャされ、最大で 30 ミリ秒がフック関数に費やされます (ほとんどの場合、10 ミリ秒未満)。フック関数は、別のスレッドによって処理されるキューにイベントを追加するだけです。独自の Application.Run() を使用して、独自の優先度の高いスレッドで実行されています。
ただし、iTunes 内 (プログラムでイベントを生成する再生/一時停止のクリックなど) またはプログラム内 (オプション ウィンドウを開くなど) を開始すると、フック関数が呼び出されなくなります。これは、キーボードをまったく使用していない場合でも発生する可能性があります (たとえば、iTunes で起動し、再生をクリックして一時停止し、キーを押した場合など)。
フックが呼び出されない原因は、フック関数で費やされた時間が長すぎるためではありません。
UnhookWindowsHookEx を呼び出すと、フック関数がまだ呼び出されているかどうかに関係なく、常に true が返されます。
では、何が原因でしょうか?
1 つの考え (証拠も解決策もありませんが) は、マネージ スレッドがもはや正しいネイティブ スレッドではないということです。私は自分のプログラムで多数の (マネージド) スレッドを使用しており、1 つのネイティブ スレッドで多数のマネージド スレッドを実行でき、マネージド スレッドが実行するネイティブ スレッドを変更できることを読みました。フックがまだメッセージを生成しているが、それらを間違ったスレッドに送信している可能性はありますか? この場合、どうすれば回避できますか?
編集:フックとコールバック
私のKeyMonitorのわずかに削除されたバージョン。わかりやすくするために削除されています。いくつかのユーティリティ (Key 列挙型のほとんどの値や、ToString() や FromString() などの Keys クラスの多くの関数など) といくつかのエラー処理を削除しました。
重要なもののほとんどは KeyMonitor クラスにあります。KeyMonitor.Start() はメッセージのスレッドを開始します。KeyMonitor.HookThread() はそのスレッドであり、メッセージ ループの Application.Run() と共にフックを作成します。KeyMonitor.KeyboardHookProc() はコールバック関数であり、KeyMonitor です。 HookEventDispatchThread() は、コールバックによって記録されたイベントをディスパッチするものです。
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace KeyTest
{
enum Key : int
{
Shift = 0x10, Ctrl, Alt,
Left_Win = 0x5B, Right_Win,
Left_Shift = 0xA0, Right_Shift, Left_Ctrl, Right_Ctrl, Left_Alt, Right_Alt,
}
class Keys
{
[DllImport("user32.dll")]
private static extern int GetKeyboardState(byte[] pbKeyState);
public const int Count = 256; // vkCode are from 1 to 254, but GetKeyboardState uses 0-255
private readonly bool[] keys = new bool[Count];
public Keys() { }
private void DoModifier(Key x, Key l, Key r) { keys[(int)x] = keys[(int)l] || keys[(int)r]; }
private void DoModifiers()
{
DoModifier(Key.Shift, Key.Left_Shift, Key.Right_Shift);
DoModifier(Key.Ctrl, Key.Left_Ctrl, Key.Right_Ctrl);
DoModifier(Key.Alt, Key.Left_Alt, Key.Right_Alt);
}
private void DoModifier(Key x, Key l, Key r, Key k) { if (k == l || k == r) keys[(int)x] = keys[(int)l] || keys[(int)r]; }
private void DoModifiers(Key k)
{
DoModifier(Key.Shift, Key.Left_Shift, Key.Right_Shift, k);
DoModifier(Key.Ctrl, Key.Left_Ctrl, Key.Right_Ctrl, k);
DoModifier(Key.Alt, Key.Left_Alt, Key.Right_Alt, k);
}
public bool this[int i] { get { return this.keys[i]; } set { this.keys[i] = value; DoModifiers((Key)i); } }
public bool this[Key k] { get { return this.keys[(int)k]; } set { this.keys[(int)k] = value; DoModifiers(k); } }
public void LoadCurrentState()
{
byte[] keyState = new byte[Count];
if (GetKeyboardState(keyState) != 0)
for (int i = 0; i < Count; ++i)
keys[i] = (keyState[i] & 0x80) != 0;
DoModifiers();
}
}
static class KeyMonitor
{
#region Windows API
private delegate int HookProc(int nCode, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll")]
private static extern int CallNextHookEx(int idHook, int nCode, UIntPtr wParam, IntPtr lParam);
private const int WH_KEYBOARD_LL = 13;
private readonly static UIntPtr WM_KEYDOWN = new UIntPtr(0x100), WM_SYSKEYDOWN = new UIntPtr(0x104);
#endregion
public static event KeyEventHandler OverridingKeyChange;
public static event KeyEventHandler KeyChange;
private struct KeyEventData { public int vk; public bool down; }
private static int hook = 0;
private static Thread dispatchThread = null, hookThread = null;
private static Keys keys = new Keys();
private static Queue<KeyEventData> queue = new Queue<KeyEventData>();
private static void Enqueue(int vk, bool down)
{
lock (queue)
{
queue.Enqueue(new KeyEventData() { vk = vk, down = down });
Monitor.Pulse(queue);
}
}
public static Keys Keys { get { return keys; } }
public static void Start()
{
if (hook == 0)
{
dispatchThread = new Thread(HookEventDispatchThread);
hookThread = new Thread(HookThread);
hookThread.Priority = ThreadPriority.Highest;
dispatchThread.Start();
hookThread.Start();
}
}
public static void Stop()
{
if (hook != 0)
{
// Minimal cleanup...
UnhookWindowsHookEx(hook);
Application.Exit();
dispatchThread.Interrupt();
}
}
private static void HookThread()
{
hook = SetWindowsHookEx(WH_KEYBOARD_LL, new HookProc(KeyboardHookProc), IntPtr.Zero, 0);
if (hook == 0) { /* Handle error */ }
keys.LoadCurrentState();
Application.Run();
}
private static int KeyboardHookProc(int nCode, UIntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
Enqueue(Marshal.ReadInt32(lParam), wParam == WM_SYSKEYDOWN || wParam == WM_KEYDOWN);
return CallNextHookEx(hook, nCode, wParam, lParam);
}
private static void HookEventDispatchThread()
{
for (; ; )
{
KeyEventData data;
lock (queue)
{
if (queue.Count == 0)
try
{
Monitor.Wait(queue);
}
catch (ThreadInterruptedException) { return; }
data = queue.Dequeue();
}
if (data.vk == -1)
{
// Done!
keys = new Keys();
queue.Clear();
return;
}
else if (keys[data.vk] == data.down)
continue;
keys[data.vk] = data.down;
KeyEventArgs e = new KeyEventArgs((System.Windows.Forms.Keys)data.vk);
if (OverridingKeyChange != null) OverridingKeyChange(null, e);
if (!e.Handled && KeyChange != null) KeyChange(null, e);
}
}
}
}