What can cause Windows to unhook a low level (global) keyboard hook?
Asked Answered
S

7

25

We have some global keyboard hooks installed via SetWindowsHookEx with WH_KEYBOARD_LL that appear to randomly get unhooked by Windows.

We verified that they hook was no longer attached because calling UnhookWindowsHookEx on the handle returns false. (Also verified that it returns true when it was working properly)

There doesn't seem to be a consistent repro, I've heard that they can get unhooked due to timeouts or exceptions getting thrown, but I've tried both just letting it sit on a breakpoint in the handling method for over a minute, as well as just throwing a random exception (C#) and it still appears to work.

In our callback we quickly post to another thread, so that probably isn't the issue. I've read about solutions in Windows 7 for setting the timeout higher in the registry because Windows 7 is more aggressive about the timeouts apparently (we're all running Win7 here, so not sure if this occurs on other OS's) , but that doesn't seem like an ideal solution.

I've considered just having a background thread running to refresh the hook every once in a while, which is hackish, but I don't know of any real negative consequences of doing that, and it seems better than changing a global Windows registry setting.

Any other suggestions or solutions? Both the class that sets the hooks and the delegates they are attached to are static, so they shouldn't be getting GC'd.

EDIT: Verified with calls to GC.Collect(); that they still work, so they are not getting garbaged collected.

Silicic answered 16/4, 2010 at 18:22 Comment(8)
Having the thread that made the SetWindowsHookEx call exit or terminate will do this.Siebert
@Hans That's not it for us, but it could help someone else. They're hooked up up right before Application.Run and unhooked right after.Silicic
I seem to recall that Managed Threads are not 1:1 with Unmanaged Threads. Perhaps the Unmanaged thread terminated?Braithwaite
As far as the timeout goes, have you tried System.Threading.Thread.Sleep() in your hook callback? Could be a long shot, but using an IDE breakpoint might not be an exact representation of a blocked hook callback.Purusha
@six I don't believe that is the case, but do you know how I could check? We have the following code GlobalHooks.InstallHooks(); Application.Run(mainForm); GlobalHooks.UninstallHooks(); which call all the SetWindowsHookEx and UnhookWindowsHookExrespectively. If the thread dies, the application should also close since it's all on the Main thread.Silicic
@Zach tried adding a System.Threading.Thread.Sleep(10000); to the callback and it still works. We're definitely not blocking over 10 seconds, should be way less than 1/4 second.Silicic
Presumably you have compared code and Interop declarations with the KB article support.microsoft.com/kb/318804 I'd suspect Interop/.Net rather than Windows. If you force a GC does it have any effect?Cabbageworm
Forced call to GC.Collect(); and they still work.Silicic
C
19

I think this has to be a timeout issue.

Other developers have reported a Windows7 specific problem with low level hooks being unhooked if they exceed an (undocumented) timeout value.

See this thread for other developers discussing the same problem. It may be that you need to perform a busy loop (or a slow Garbage Collection) rather than a Sleep to cause the unhooking behavior. A breakpoint in the LowLevelKeyboardProc function might also create timeout problems. (There's also the observation that a heavy CPU load by another task might provoke the behavior - presumably because the other task steals CPU cycles from the LowLevelKeyboardProc function and causes it to take too long.)

The solution suggested in that thread is to try setting the LowLevelHooksTimeout DWORD value in the registry at HKEY_CURRENT_USER\Control Panel\Desktop to a larger value.

Remember that one of the glories of C# is that even simple statements can take an inordinate amount of time if a garbage collection occurs.. This (or CPU loading by other threads) might explain the intermittent nature of the problem.

Cabbageworm answered 21/4, 2010 at 3:46 Comment(1)
You don't have to go hunting through the MSDN forums to confirm this; it's quite well documented here in the SDK docs. Focus on the section titled "Remarks".Elutriate
P
3

There are two things I have thought of that might help you figure out where the problem is.

  1. To help isolate the problem's location, run another WH_KEYBOARD_LL hook simultaneously with your current hook and have it do nothing other than pass the data on down the hook chain. When you find out that your original hook is unhooked, check and see if this "dummy" hook was also unhooked. If the "dummy" hook was also unhooked, you can be fairly certain the problem is outside of your hook (i.e. in Windows or something related to your process as a whole?) If the "dummy" hooks was not unhooked, then the problem is probably somewhere within your hook.

  2. Log the information that comes to your hook via the callback and run it until the hook gets unhooked. Repeat this a number of times and examine the logged data to see if you can discern a pattern leading up to the unhooking.

I would try these one at a time, just in case either would affect the others' outcome. If after that you have no leads on what the problem could be, you might try running them together.

Purusha answered 20/4, 2010 at 20:26 Comment(4)
We actually have 3 WH_KEYBOARD_LL hooks, each of which ends with CallNextHookEx When it stops working they all stop working and nothing gets hit with breakpoints set in all the callbacks.Silicic
It's also kinda frustrating because it's not consistent, sometimes I can get it to unhook after a few minutes, other times it'll stay hooked for hours, so it's pretty annoying to debug.Silicic
@Davy8: Are you able to reproduce the problem with only one WH_KEYBOARD_LL hook?Purusha
I'm having a similar problem. To do a test I've attached two keyboard hooks: after I hit a break point only one of them was removed. In that scenario shouldn't windows remove all hooks? Another strange thing is that also a mousehook that was installed was removed.Wineglass
T
2

It's a long shot, but by any chance do you have anti-virus software running? That could very well be noticing a keyboard hook and kicking it out.

It's more likely it would warn you, and remove it immediately, but it's one of those odd things worth checking.

Threesome answered 26/4, 2010 at 3:23 Comment(0)
O
1

Perhaps someone else has a hook that isn't calling CallNextHookEx()?

Osmium answered 21/4, 2010 at 3:47 Comment(1)
I don't believe so because when it gets in this state, UnhookWindowsHookEx on the hook returns false indicating that the hook was gone.Silicic
C
1

I know it is a ugly solution but you can set a Timer for ever 5 minutes then you can re-hook your keyboards events?

I had a same problem with Win7 machine, after a while keyboard hooks were losing it's reference, and I had to set up a Timer for every 5 minutes to hook events again, and now it keeps it fresh.

Claus answered 27/6, 2013 at 18:54 Comment(0)
F
1

I'm using the following project from: http://www.codeproject.com/Articles/7294/Processing-Global-Mouse-and-Keyboard-Hooks-in-C to perform tasks when a certain key has been pressed.

I noticed that after i performed a task with a hotkey it stopped listening for new keypresses, i then changed the KeyboardHookListener to static and it seemed to solve my problem. I've no idea why this solved my problem, so feel free to comment on this!

My hotkey class:

 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();
        }
    }
}
Finespun answered 6/4, 2014 at 16:9 Comment(0)
L
0

Very late but thought i'd share something. Gathering from here there's a couple hints like putting the SetWindowsHookEx in a separate thread and that it's more a scheduling problem.

I also noticed that Application.DoEvents was using quite a bit of CPU, and found that PeekMessage uses less.

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();
    }
}

so just change the active bool to false in Deactivate event and call HookMouse in the Activated event.

HTH

EDIT: I noticed games were slowed down with this, so decided to unhook when app is not active using Activated and Deactivate events.

Loquacity answered 22/4, 2018 at 12:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.