我正在尝试在 Windows 挂钩上制作热键。它有效,但有时我使用此解决方案的 wpf 窗口抛出 System.ExecutionEngineException(当我快速按下很多按钮时经常发生这种情况。我发现这是因为消息循环被我的钩子破坏了。是否有一些避免它的方法?
附注我知道 RegisterHotKey 功能,但如果我的热键被激活,我需要中止按键压力的功能(以防止在其他程序中执行类似的热键)。
这是我的挂钩设置:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc 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.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
这是我的钩子:
private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
bool isKeyProcessed = false;
try
{
if (nCode >= 0 && (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN))
{
int vkCode = Marshal.ReadInt32(lParam);
isKeyProcessed = KeyDown?.Invoke((Keys)vkCode) ?? false;
}
else if (nCode >= 0 && (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP))
{
int vkCode = Marshal.ReadInt32(lParam);
isKeyProcessed = KeyUp?.Invoke((Keys)vkCode) ?? false;
}
}
catch (Exception ex)
{
isKeyProcessed = false;
try
{
ExceptionHappend?.Invoke(ex);
}
catch { }
}
if (!isKeyProcessed)
return CallNextHookEx(_hookID, nCode, wParam, lParam);
else
return (IntPtr)1;
}
有我的钩子事件处理程序(对于按键按下,在按键上我只是从当前键组合中删除向上按钮)
public bool HandleKeyDown(Keys key)
{
lock (_lock)
{
currentCombo.Keys.Add(key);
if (hotkeys.ContainsKey(currentCombo))
{
RunHandler(hotkeys[currentCombo]);
currentCombo.Keys.Clear();
return true;
}
return false;
}
}
private void RunHandler(Action handler)
{
_runningActions = _runningActions.Where(x=>x.Status == TaskStatus.Running).ToList();
var task = new Task(handler);
task.Start();
_runningActions.Add(task);
}
最后,我的热键,例子:
public void ShowWorkedQ()
{
Dispatcher.Invoke(new Action(() =>
{
OutLabel.Text = "Pressed Alt+Q";
}));
}
感谢您的长时间阅读!
看起来你没有让回调委托保持活动状态。您需要将它存储在一个字段中,以防止它被破坏。
当您将委托传递给 PInvoke 函数时,PInvoke 将创建一个小的本机函数,使回调能够跳回托管代码。但是 GC 无法正确跟踪此对象,除非您仍然持有对原始委托的引用。
static LowLevelKeyboardProc _proc;
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
if (_proc != null) throw new Exception("Cannot set hook more than once");
_proc = proc; // NEW!
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
还请注意,低级 KB 钩子中的
lParam
实际上表示指向 KBDLLHOOKSTRUCT
的指针。虽然您当前的代码 techincally 只能读取第一个值,但您应该真正编组整个结构。
[StructLayout(LayoutKind.Sequential)]
struct KBDLLHOOKSTRUCT
{
int vkCode;
int scanCode;
int flags;
int time;
IntPtr dwExtraInfo;
}
delegate IntPtr LowLevelKeyboardProc (
int nCode,
IntPtr wParam,
[In]
in KBDLLHOOKSTRUCT lParam
);