【发布时间】:2018-07-19 15:03:00
【问题描述】:
键盘挂钩未触发事件并在 dispose 时引发 win32 异常
我的 c# 应用程序创建键盘钩子用于处理键盘事件(许多读卡器、扫描仪和其他 POS 设备模拟键盘)。 有时我的应用程序会创建没有错误的键盘钩子,但它不会触发事件并且在 dispose 时会抛出异常:
System.ComponentModel.Win32Exception (0x80004005):无法删除“应用程序”的键盘挂钩。错误 1404:无效的钩子句柄
其他日志条目是相同的错误,但它告诉了
ERROR_NOT_ALL_ASSIGNED
Source code of library and demo app.
我无法在我的电脑上重现此问题,也不知道我应该调查什么或谷歌。 我知道:
- 所有出现这种奇怪行为的客户端都使用 x86 操作系统。
- Windows 特权或权限可能存在一些问题。
- 有时会中断(并非总是如此)。
- 库面向 .NET 4
- 应用程序面向 .NET 4.5.1
- 编译平台:任何 CPU
另外,我不熟悉非托管代码和 win api。我从某个线程中获得了这段代码,并根据我的需要对其进行了修改,但抽象程度很高。
钩子:
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);
【问题讨论】:
-
很确定您应该调用
SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, IntPtr.Zero, GetCurrentThreadId ());,即不要为 module 传递值 -
@MickyD 不,LL 挂钩是全局的。
-
如果 .NET 应用程序在我的系统上安装了全局挂钩,它将立即被卸载。
-
LoadLibrary() 和 FreeLibrary() 声明错误,它们缺少 SetLastError = true。代码中没有防止意外调用 SetWindowsHookEx() 两次的保护措施。当垃圾收集器运行和 1404 错误时,这将导致随机崩溃。 ERROR_NOT_ALL_ASSIGNED 是一个安全错误代码,表示用户帐户没有足够的权限。我能看到的就这些了。
-
@Anders 当然是的。谢谢
标签: c# winapi keyboard unmanaged