C# SetWindows钩子停止工作 键盘挂钩未触发事件并在dispose上引发win32异常
我的c#应用程序创建用于处理键盘事件的键盘挂钩(许多读卡器、扫描仪和其他POS设备模拟键盘)。 有时,我的应用程序创建键盘钩子时不会出错,但不会触发事件并在dispose时抛出异常: System.ComponentModel.Win32Exception(0x80004005):无法删除“应用”的键盘挂钩。错误1404:钩子句柄无效 另一个日志条目是相同的错误,但它说明了 错误\u未\u分配所有\u 我不能在我的电脑上重现这个问题,也不知道我应该调查什么或谷歌。 我知道:C# SetWindows钩子停止工作 键盘挂钩未触发事件并在dispose上引发win32异常,c#,winapi,keyboard,unmanaged,C#,Winapi,Keyboard,Unmanaged,我的c#应用程序创建用于处理键盘事件的键盘挂钩(许多读卡器、扫描仪和其他POS设备模拟键盘)。 有时,我的应用程序创建键盘钩子时不会出错,但不会触发事件并在dispose时抛出异常: System.ComponentModel.Win32Exception(0x80004005):无法删除“应用”的键盘挂钩。错误1404:钩子句柄无效 另一个日志条目是相同的错误,但它说明了 错误\u未\u分配所有\u 我不能在我的电脑上重现这个问题,也不知道我应该调查什么或谷歌。 我知道: 所有具有这种奇怪
- 所有具有这种奇怪行为的客户端都使用x86操作系统
- windows特权或权限可能存在一些问题
- 它有时(不总是)坏
- 图书馆目标.NET4
- 应用程序目标.NET 4.5.1
- 编译平台:任何CPU
public GlobalKeyboardHook()
{
_windowsHookHandle = IntPtr.Zero;
_user32LibraryHandle = IntPtr.Zero;
_hookProc = LowLevelKeyboardProc; // we must keep alive _hookProc, because GC is not aware about SetWindowsHookEx behaviour.
_user32LibraryHandle = LoadLibrary("User32");
if (_user32LibraryHandle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode,
$"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
_windowsHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, _user32LibraryHandle, 0);
if (_windowsHookHandle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode,
$"Failed to adjust keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
}
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("USER32", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
吊钩处理:
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// because we can unhook only in the same thread, not in garbage collector thread
if (_windowsHookHandle != IntPtr.Zero)
{
if (!UnhookWindowsHookEx(_windowsHookHandle))
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode,
$"Failed to remove keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
_windowsHookHandle = IntPtr.Zero;
// ReSharper disable once DelegateSubtraction
_hookProc -= LowLevelKeyboardProc;
}
}
if (_user32LibraryHandle != IntPtr.Zero)
{
if (!FreeLibrary(_user32LibraryHandle)) // reduces reference to library by 1.
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode,
$"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
_user32LibraryHandle = IntPtr.Zero;
}
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool FreeLibrary(IntPtr hModule);
[DllImport("USER32", SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hHook);
如果你在钩子程序中做错了什么,窗口会在不告诉你的情况下解开钩子
- 在您的
中,您没有检查底层键盘proc
!必须首先执行此操作,如果nCode
为<0,则必须返回nCode
,而无需任何其他处理CallNextHookEx
- 你不能在hook进程中花费太多时间。AFAIK未记录确切限制,并且可以通过注册表值更改。为了安全起见,您应该将目标设定为<200ms
您可以尝试在有问题的系统上设置/更改
低级别hookstimeout
注册表值,看看这是否有帮助。如果您在hook proc中出错,窗口将在不通知您的情况下解除锁定
- 在您的
中,您没有检查底层键盘proc
!必须首先执行此操作,如果nCode
为<0,则必须返回nCode
,而无需任何其他处理CallNextHookEx
- 你不能在hook进程中花费太多时间。AFAIK未记录确切限制,并且可以通过注册表值更改。为了安全起见,您应该将目标设定为<200ms
您可以尝试在有问题的系统上设置/更改
低级别hookstimeout
注册表值,看看是否有帮助。非常确定您应该调用setWindowshookx(WH_-KEYBOARD\LL,_-hookProc,IntPtr.Zero,GetCurrentThreadId())代码>即不传递module@MickyD否,LL钩子仅为全局钩子。如果.NET应用程序在我的系统上安装了全局钩子,它将立即被卸载。LoadLibrary()和FreeLibrary()声明错误,它们缺少SetLastError=true。代码中没有防止意外调用SetWindowsHookEx()两次的保护。这将以垃圾收集器运行时的随机崩溃和1404错误而告终。ERROR_NOT_ALL_ASSIGNED是一个安全错误代码,表示用户帐户没有足够的权限。“这就是我所能看到的一切。”安德斯:是的,当然。感谢您确实应该调用setWindowshookx(WH_KEYBOARD_LL,_hookProc,IntPtr.Zero,GetCurrentThreadId())代码>即不传递module@MickyD否,LL钩子仅为全局钩子。如果.NET应用程序在我的系统上安装了全局钩子,它将立即被卸载。LoadLibrary()和FreeLibrary()声明错误,它们缺少SetLastError=true。代码中没有防止意外调用SetWindowsHookEx()两次的保护。这将以垃圾收集器运行时的随机崩溃和1404错误而告终。ERROR_NOT_ALL_ASSIGNED是一个安全错误代码,表示用户帐户没有足够的权限。“这就是我所能看到的一切。”安德斯:是的,当然。谢谢你的回答!我会调查的。如果我将添加Thread.Sleep in callback,我可以在我的电脑上复制它吗?据我所知,低级别的钩子刺激只存在于windows 7系统上?所有出现此问题的客户端都运行windows 7。我发现我猜是Windows7和更新版本。所有系统都有一个超时(你不能在超时后阻止/忽略一个键,因为它会忽略你的返回值),但只有7+会解除你的锁定(IIRC)。伙计,你救了我的命。在windows 10的回调中添加Thread.Sleep(2000)后,我在电脑上重现了这种行为!如果您从未真正需要执行/中止击键,那么您可能希望通过对PostMessage的简单调用来实现钩子过程,该调用将消息发布到另一个线程上的某个窗口。谢谢您的回答!我会调查的。如果我将添加Thread.Sleep in callback,我可以在我的电脑上复制它吗?据我所知,低级别的钩子刺激只存在于windows 7系统上?所有出现此问题的客户端都运行windows 7。我发现我猜是Windows7和更新版本。所有系统都有一个超时(你不能在超时后阻止/忽略一个键,因为它会忽略你的返回值),但只有7+会解除你的锁定(IIRC)。伙计,你救了我的命。在windows 10的回调中添加Thread.Sleep(2000)后,我在电脑上重现了这种行为!如果您从未真正需要执行/中止击键,那么您可能希望将钩子过程实现为对PostMessage的简单调用,将消息发布到另一个线程上的某个窗口。