I have a keyboard hook that works great but has one issue I can't resolve. I want the hook to run on async and I'm not able to do it. Normally I use it like this
private void button1_Click(object sender, EventArgs e)
{
var res = new InterceptKeys();
res.startHook();
}
When I click button 1 the hooks starts and works correctly. But when i click the button 2 ...
private void button2_Click(object sender, EventArgs e)
{
Thread.Sleep(100000) //simulates an operation that keeps form busy
}
Now the hook doesn't work anymore and have to wait for the sleep to finish. My question is, it is possible to make the hook async? I tried to call it like a task, but the hook doesn't work.
public class InterceptKeys
{
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hwnd, StringBuilder ss, int count);
[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);
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private IntPtr _hookID = IntPtr.Zero;
public void startHook()
{
if(_hookID == IntPtr.Zero)
{
_hookID = SetHook();
}
}
public void endHook()
{
UnhookWindowsHookEx(_hookID);
}
private IntPtr SetHook()
{
return SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, IntPtr.Zero, 0);
}
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
int cont = 0;
Dictionary<cKeyLogger.VK, Tuple<int, string>> combinacions = new Dictionary<cKeyLogger.VK, Tuple<int, string>> { { cKeyLogger.VK.F3, new Tuple<int, string>(2,"{r}{UP}") }, { cKeyLogger.VK.F4, new Tuple<int, string>(2, "{R}{DOWN}") } };
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
cKeyLogger.VK vkCode = (cKeyLogger.VK)Marshal.ReadInt32(lParam);
string window = ActiveWindowTitle();
if(wParam == (IntPtr)WM_KEYDOWN)
{
Console.WriteLine(vkCode + " _ " + window);
if (window.ToLower().Contains("excel")) //just applies to excel to do some testing
{
//Do something
}
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
private static string ActiveWindowTitle()
{
//Create the variable
const int nChar = 256;
StringBuilder ss = new StringBuilder(nChar);
//Run GetForeGroundWindows and get active window informations
//assign them into handle pointer variable
IntPtr handle = IntPtr.Zero;
handle = GetForegroundWindow();
if (GetWindowText(handle, ss, nChar) > 0) return ss.ToString();
else return "";
}
}
I'd be wary of trying to tackle this problem by moving the hook to a separate thread. If you use a separate thread for the keyboard hook, when your code eventually hits the point where it needs to do something here:
If there is a chance that the UI thread is still going to be blocked at this point, the code to "do something" is still going to be delayed. That delay in calling CallNextHookEx will actually get your keyboard hook removed by the OS.
I try to visualise hooks as chain links, when your application installs a hook it has a duty to do what it needs to as fast as possible before calling CallNextHookEx so that the next application can process the hook and Windows messages can keep flowing. If the OS detects an application is destabilising this it will remove the hook.
My question to you is: can move the blocking process from the UI thread into a separate thread? Rather than trying to move the hook into a separate thread. I've written many applications which use hooks and this is the way I've always done it.
Hope this helps.