Global keyboard hook
Asked Answered
S

2

1

I need to capture global keyboard messages, so I use SetWindowsHookEx() with WH_KEYBOARD_LL. But it only works when application is in focus and does not trigger Callback globally. Almost the same code works great with mouse_LL(with another structure& etc.) Please help!

public const int WH_KEYBOARD_LL = 13;
public const int VK_INSERT = 0x2D;
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
HookProc KeyboardHookProcedure;

[DllImport("user32.dll", CharSet = CharSet.Auto,
   CallingConvention = CallingConvention.StdCall)]
  public static extern int SetWindowsHookEx(int idHook, HookProc lpfn,
  IntPtr hInstance, int threadId);

[DllImport("user32.dll", CharSet = CharSet.Auto,
   CallingConvention = CallingConvention.StdCall)]
  public static extern bool UnhookWindowsHookEx(int idHook);

[DllImport("user32.dll", CharSet = CharSet.Auto,
   CallingConvention = CallingConvention.StdCall)]
  public static extern int CallNextHookEx(int idHook, int nCode,
  IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  private static extern IntPtr GetModuleHandle(string lpModuleName);

[StructLayout(LayoutKind.Sequential)]
  private struct KBDLLHOOKSTRUCT
  {
     public uint vkCode;
     public uint scanCode;
     public uint flags;
     public uint time;
     public IntPtr dxExtraInfo;
  }

private void SetHookKeyboard()
  {
     if (kHook == 0)
     {
        KeyboardHookLL();

        //If the SetWindowsHookEx function fails.
        if (kHook == 0)
        {
           MessageBox.Show("SetWindowsHookEx Failed");
           return;
        }
        button1.Text = "UnHook Windows Hook";
     }
     else
     {
        bool ret = UnhookWindowsHookEx(kHook);
        //If the UnhookWindowsHookEx function fails.
        if (ret == false)
        {
           MessageBox.Show("UnhookWindowsHookEx Failed");
           return;
        }
        kHook = 0;
        this.Text = "Keyboard Hook";
     }
  }

private void KeyboardHookLL()
  {
     KeyboardHookProcedure = new HookProc(MainForm.KeyboardHookProc);
     kHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure,   GetModuleHandle("user32"), 0);
  }

public static int KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
  {
     KBDLLHOOKSTRUCT MyKeyboardHookStruct = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
     if (nCode < 0)
     {
        return CallNextHookEx(hHook, nCode, wParam, lParam);
     }
     else
     {
        Form tempForm = Form.ActiveForm;
        tempForm.Text = MyKeyboardHookStruct.vkCode.ToString();
        if (MyKeyboardHookStruct.vkCode == VK_INSERT)
        {
           MainForm.botAlive = false;
           MessageBox.Show(MainForm.botAlive.ToString());
        }
        return CallNextHookEx(hHook, nCode, wParam, lParam);
     }
  }
Stob answered 21/3, 2013 at 12:47 Comment(4)
in general it is safer to use hotkeys instead of hooks. Is there a special reason why you need a hook instead of a hotkey?Greenbelt
Yeap, I'm gonna need to capture every input, as it's going to be a "recorder".Stob
if this is the code of your mainform, then shouldn't you registrate the eventhandler somewhere?Greenbelt
this is a selection from the overall code. KeyboardHookProc is a callback method, so I don't have to register any eventhandler. It catches keyboard input(but only when the calling app is in focus and i need it to catch global keyboard messages)Stob
S
0

The problem was in "debug" feature.

Form tempForm = Form.ActiveForm;
Stob answered 29/3, 2013 at 7:56 Comment(0)
A
1

Credit goes to Jon here:

int vs IntPtr when you have a handle?

I know this is an old post and I digress a bit, but I noticed a kinda sinister shortcoming in the original code which can turn around and bite you very very bad in due time (I know it did in my case):

  [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
  public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

Should more preferably be:

  [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
   static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId);

Similar changes should be made to the other signatures as well. Even though microsoft itself has resources that show SetwindowsHookEx with an int as a return type (https://support.microsoft.com/en-us/kb/318804), you should never lightheartedly substitute 'IntPtr' with some other type. In this case 'int' is equivalent to 'IntPtr' only in 32bit OSes. In 64bit platforms 'IntPtr' is 64bit while 'int' keeps on being only 32bits which opens a whole can of worms. One of the worst aspects of using 'int' is that if the returned value of SetWindowsHookEx can't fit in it then you will most probably end up with a mangled int-handle (its rare but not inconceivable).

This means that if your application's lifetime extends past the point that you unhook/dispose of the hook (not that the unhook call will work at all to begin with ...) then you will might end up freezing the mouse and/or keyboard completely until the host process is killed. Most people will simply reboot the machine at that point and dispose of your app altogether. All this "keyboard/mouse freeze" thing happens because the undisposed hook needs to reach the message pump of your host application, which obviously can't happen since the component it belonged to has gone bust during dispose - wooohoo!

TL;DR: Pay attention the signatures of the p/invoke methods you are using and IntPtrs in particular.

Amphioxus answered 23/3, 2016 at 20:43 Comment(0)
S
0

The problem was in "debug" feature.

Form tempForm = Form.ActiveForm;
Stob answered 29/3, 2013 at 7:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.