Что может заставить Windows отцепить низкоуровневую (глобальную) зацепку клавиатуры?
У нас есть несколько глобальных клавиатурных хуков, установленных через SetWindowsHookEx
с WH_KEYBOARD_LL
которые кажутся случайным образом отцепленными Windows.
Мы убедились, что они больше не подключены, потому что звонят UnhookWindowsHookEx
на ручке возвращается false
, (Также проверено, что он возвращает true
когда все заработало нормально)
Похоже, что нет последовательного воспроизведения, я слышал, что они могут отсоединиться из-за тайм-аутов или исключений, но я попробовал и то и другое, просто оставив его на точке останова в методе обработки более минуты, а также просто выбрасывает случайное исключение (C#), и оно все еще работает.
В нашем обратном вызове мы быстро отправляем сообщения в другую ветку, так что, вероятно, проблема не в этом. Я читал о решениях в Windows 7 для увеличения времени ожидания в реестре, потому что Windows 7, по-видимому, более агрессивно относится к тайм-аутам (здесь мы все запускаем Win7, поэтому не уверен, происходит ли это в других ОС), но это не так. не кажется идеальным решением.
Я подумал о том, чтобы просто запустить фоновый поток, чтобы периодически обновлять ловушку, что является хакерским, но я не знаю никаких реальных негативных последствий этого, и это кажется лучше, чем изменение глобального параметра реестра Windows.,
Любые другие предложения или решения? И класс, который устанавливает хуки, и делегаты, к которым они присоединены, являются статическими, поэтому они не должны получать GC'd.
РЕДАКТИРОВАТЬ: проверено с звонками в GC.Collect();
что они по-прежнему работают, поэтому они не получают мусора.
7 ответов
Я думаю, что это должно быть проблемой тайм-аута.
Другие разработчики сообщали о специфической проблеме Windows7 с отцеплением низкоуровневых хуков, если они превышают (недокументированное) значение тайм-аута.
Посмотрите эту ветку для других разработчиков, обсуждающих ту же проблему. Возможно, вам потребуется выполнить занятый цикл (или медленную сборку мусора), а не режим сна, чтобы вызвать отсоединение. Точка останова в функции LowLevelKeyboardProc также может создавать проблемы тайм-аута. (Существует также наблюдение, что большая загрузка ЦП другой задачей может спровоцировать такое поведение - предположительно потому, что другая задача крадет циклы ЦП из функции LowLevelKeyboardProc и приводит к тому, что она занимает слишком много времени.)
Решение, предложенное в этом потоке, состоит в том, чтобы попытаться установить значение DWORD LowLevelHooksTimeout в реестре в HKEY_CURRENT_USER\Control Panel\Desktop на большее значение.
Помните, что одним из достоинств C# является то, что даже простые операторы могут занимать чрезмерное количество времени, если происходит сборка мусора. Это (или загрузка ЦП другими потоками) может объяснить прерывистый характер проблемы.
Я подумал о двух вещах, которые могут помочь вам понять, в чем проблема.
Чтобы помочь локализовать проблему, запустите другую
WH_KEYBOARD_LL
перехватывать одновременно с вашим текущим перехватом, и пусть он не делает ничего, кроме передачи данных по цепочке перехватчиков. Когда вы обнаружите, что ваш оригинальный крючок отцеплен, проверьте и посмотрите, не был ли этот крючок "пустышки". Если "фиктивный" хук также был отцеплен, вы можете быть совершенно уверены, что проблема не в вашей хуке (то есть в Windows или в чем-то, что связано с вашим процессом в целом?) Если "фиктивные" хуки не были отцеплены, то проблема вероятно где-то в твоем крючкеЗаписывайте информацию, которая поступает на ваш хук через обратный вызов, и запускайте ее, пока хук не отцепится. Повторите это несколько раз и изучите зарегистрированные данные, чтобы увидеть, можете ли вы распознать схему, ведущую к отцеплению.
Я бы попробовал их по одному, на случай, если это повлияет на исход других. Если после этого у вас нет сведений о том, в чем может быть проблема, вы можете попробовать запустить их вместе.
Это долгий путь, но случайно ли у вас работает антивирус? Это вполне может быть замечением зацепки клавиатуры и ее выбивания.
Скорее всего, он предупредит вас и немедленно удалит, но это одна из тех странных вещей, которые стоит проверить.
Я знаю, что это уродливое решение, но вы можете установить таймер на 5 минут, а затем переназначить события клавиатуры?
У меня была та же проблема с машиной Win7, через некоторое время перехваты клавиатуры теряли свою привязку, и мне приходилось устанавливать таймер на каждые 5 минут, чтобы перехватывать события снова, и теперь он сохраняет его свежим.
Я использую следующий проект: http://www.codeproject.com/Articles/7294/Processing-Global-Mouse-and-Keyboard-Hooks-in-C для выполнения задач при нажатии определенной клавиши.
Я заметил, что после того, как я выполнил задачу с помощью горячей клавиши, она перестала слушать новые нажатия клавиш, затем я изменил KeyboardHookListener на статический, и это, похоже, решило мою проблему. Я понятия не имею, почему это решило мою проблему, поэтому не стесняйтесь комментировать это!
Мой класс горячих клавиш:
class Hotkey
{
private static KeyboardHookListener _keyboardHookListener;
public void Start()
{
_keyboardHookListener = new KeyboardHookListener(new GlobalHooker()) { Enabled = true };
_keyboardHookListener.KeyDown += KeyboardListener_OnkeyPress;
}
private void KeyboardListener_OnkeyPress(object sender, KeyEventArgs e)
{
// Let's backup all projects
if (e.KeyCode == Keys.F1)
{
// Initialize files
var files = new Files();
// Backup all projects
files.BackupAllProjects();
}
// Quick backup - one project
else if (e.KeyCode == Keys.F2)
{
var quickBackupForm = new QuickBackup();
quickBackupForm.Show();
}
}
}
Возможно, у кого-то еще есть ловушка, которая не вызывает CallNextHookEx()?
Очень поздно, но думал, что поделюсь чем-нибудь. Собираясь отсюда, есть пара подсказок, таких как помещение SetWindowsHookEx в отдельный поток, и это больше проблема планирования.
Я также заметил, что Application.DoEvents использует довольно много ресурсов процессора, и обнаружил, что PeekMessage использует меньше.
public static bool active = true;
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr handle;
public uint msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public System.Drawing.Point p;
}
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool PeekMessage(out NativeMessage message,
IntPtr handle, uint filterMin, uint filterMax, uint flags);
public const int PM_NOREMOVE = 0;
public const int PM_REMOVE = 0x0001;
protected void MouseHooker()
{
NativeMessage msg;
using (Process curProcess = Process.GetCurrentProcess())
{
using (ProcessModule curModule = curProcess.MainModule)
{
// Install the low level mouse hook that will put events into _mouseEvents
_hookproc = MouseHookCallback;
_hookId = User32.SetWindowsHookEx(WH.WH_MOUSE_LL, _hookproc, Kernel32.GetModuleHandle(curModule.ModuleName), 0);
}
}
while (active)
{
while (PeekMessage(out msg, IntPtr.Zero,
(uint)WM.WM_MOUSEFIRST, (uint)WM.WM_MOUSELAST, PM_NOREMOVE))
;
Thread.Sleep(10);
}
User32.UnhookWindowsHookEx(_hookId);
_hookId = IntPtr.Zero;
}
public void HookMouse()
{
active = true;
if (_hookId == IntPtr.Zero)
{
_mouseHookThread = new Thread(MouseHooker);
_mouseHookThread.IsBackground = true;
_mouseHookThread.Priority = ThreadPriority.Highest;
_mouseHookThread.Start();
}
}
поэтому просто измените активное значение bool на false в событии Deactivate и вызовите HookMouse в событии Activated.
НТН
РЕДАКТИРОВАТЬ: я заметил, что игры были замедлены с этим, поэтому решил отсоединиться, когда приложение не активно, используя события Activated и Deactivate.