Hooking the keyboard message queue in compact framework code

Print This Post Print This Post

Here is some nice code to use a keyboard hook implemented in C# for compact framework.

You can use this to catch the funny OS asigned keys like F1, F2, F3, F4, F6 and F7 (Softkey 1 and 2, Phone, End, Volume Up, Volume Down); and last but not least catch the Win Key press.

The hooking class:

using System;
using System.Runtime.InteropServices;
/*
In order to use this class in your program, just declare the varialble and hook up into HookEvent:
HookKeys hook = new HookKeys();
hook.HookEvent += new HookKeys.HookEventHandler(HookEvent);
hook.Start();
*/
public class HookKeys
{
#region delegates
    public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);
    public delegate void HookEventHandler(HookEventArgs e, KeyBoardInfo keyBoardInfo);
    public HookEventHandler HookEvent;
#endregion
#region fields
    private HookProc hookDeleg;
    private static int hHook = 0;
#endregion

    public HookKeys()
    {
    }
    ~HookKeys(){
        if(hHook!=0)
            this.Stop();
    }
    #region public methods
    ///
    /// Starts the hook
    ///
    public void Start()
    {
        if (hHook != 0)
        {
            //Unhook the previouse one
            this.Stop();
        }
        hookDeleg = new HookProc(HookProcedure);
        hHook = SetWindowsHookEx(WH_KEYBOARD_LL, hookDeleg, GetModuleHandle(null), 0);
        if (hHook == 0)
        {
            throw new SystemException("Failed acquiring of the hook.");
        }
        AllKeys(true);
    }
    ///
    /// Stops the hook
    ///
    public void Stop()
    {
        UnhookWindowsHookEx(hHook);
        AllKeys(false);
    }
    #endregion
    #region protected and private methods
    protected virtual void OnHookEvent(HookEventArgs hookArgs, KeyBoardInfo keyBoardInfo)
    {
        if (HookEvent != null)
        {
            HookEvent(hookArgs, keyBoardInfo);
        }
    }

    private int HookProcedure(int code, IntPtr wParam, IntPtr lParam)
    {
       KBDLLHOOKSTRUCT hookStruct =  (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
       if (code < 0)
            return CallNextHookEx(hookDeleg, code, wParam, lParam);
       // Let clients determine what to do
       HookEventArgs e = new HookEventArgs();
       e.Code = code;
       e.wParam = wParam;
       e.lParam = lParam;
       KeyBoardInfo keyInfo = new KeyBoardInfo();
       keyInfo.vkCode = hookStruct.vkCode;
       keyInfo.scanCode = hookStruct.scanCode;
       OnHookEvent(e, keyInfo);
       // Yield to the next hook in the chain
       return CallNextHookEx(hookDeleg, code, wParam, lParam);
   }
   #endregion
   #region P/Invoke declarations

   [DllImport("coredll.dll")]
   private static extern int AllKeys(bool bEnable);

   [DllImport("coredll.dll")]
   private static extern int SetWindowsHookEx(int type, HookProc hookProc, IntPtr hInstance, int m);
   [DllImport("coredll.dll")]
   private static extern IntPtr GetModuleHandle(string mod);
   [DllImport("coredll.dll")]
   private static extern int CallNextHookEx(
           HookProc hhk,
           int nCode,
           IntPtr wParam,
           IntPtr lParam
           );
   [DllImport("coredll.dll")]
   private static extern int GetCurrentThreadId();
   [DllImport("coredll.dll", SetLastError = true)]
   private static extern int UnhookWindowsHookEx(int idHook);
   private struct KBDLLHOOKSTRUCT
   {
       public int vkCode;
       public int scanCode;
       public int flags;
       public int time;
       public IntPtr dwExtraInfo;
   }
   const int WH_KEYBOARD_LL = 20;
   #endregion
}
#region event arguments

    public class HookEventArgs : EventArgs
    {
        public int Code;    // Hook code
        public IntPtr wParam;   // WPARAM argument
        public IntPtr lParam;   // LPARAM argument
    }
    public class KeyBoardInfo
    {
        public int vkCode;
        public int scanCode;
        public int flags;
        public int time;
    }
#endregion

Here is the download of the Visual Studio 2005 Windows Mobile 6 SDK targeting source code:

[Download not found]

Update 20. sept 2019:

Better code which will start/stop the hook with Activate/Deactivate (Show/Minimize) of a form:

using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace HookCEtest
{
    public partial class Form1 : Form
    {
        HookKeys hook;
        public Form1()
        {
            InitializeComponent();
            // Inizialize keyboard hook event handler. Calls "HookEvent" method,
            // when any hardware button is pressed on the field computer.

            hook = new HookKeys();
            hook.HookEvent += new HookKeys.HookEventHandler(HookEvent);
            //hook.Start();
        }

        private void HookEvent(HookEventArgs e, KeyBoardInfo keyBoardInfo)
        {
            /*
            This method is called, when any hardware button is pressed.
            When a defined button is pressed, foreground windows toggles background
            color.
            */
            System.Diagnostics.Debug.WriteLine(String.Format("HookEvent called: vkCode={0}", keyBoardInfo.vkCode));
            if (keyBoardInfo.vkCode == 123 && e.wParam.ToInt32() == 256) // Home button pressed
            {
                System.Diagnostics.Debug.WriteLine("Search button pressed");
                ToggleWindow();
            }

        }
        private void ToggleWindow()
        {
            // Bring window of other software to front
            Color old = this.BackColor;
            this.BackColor = Color.Blue;
            this.Refresh();
            System.Threading.Thread.Sleep(1000);
            this.BackColor = old;
        }

        private void Form1_Activated(object sender, EventArgs e)
        {
            hook.Start();
        }

        private void Form1_Deactivate(object sender, EventArgs e)
        {
            hook.Stop();
        }

    }
}

If you do not use the above, a system thread is stopped when Form is minimized:

'HookCEtest.exe' (Managed): Loaded 'C:\Program Files (x86)\Microsoft.NET\SDK\CompactFramework\v3.5\Debugger\BCL\System.Drawing.dll'
HookEvent called: vkCode=123
HookEvent called: vkCode=123
HookEvent called: vkCode=118
HookEvent called: vkCode=118
HookEvent called: vkCode=123
HookEvent called: vkCode=123
The thread 0xe9ad0012 has exited with code 0 (0x0).

The HookKeys itself does not use any custom thread!

18 Comments

  1. Bernd says:

    Thanks, this little Class saved my day!

  2. adi says:

    Thanks allot,
    The behavior is not consistent, some times one press on keyboard is being interpreted as two clicks, do you know what could be the reason?

    Adi

  3. admin says:

    ???-)

    The hook code simply enables you to see keyboard messages. You have to look for WM_KEYDOWN and/or WM_KEYUP yourself in the hookeventhandler. Every keypress on the hardware keyboard may produce two and more messages depending on the key pressed. If you press the(a) key and CAPS lock is on, you will get wm_keydown+shift; wm_keydown+’a’;(wm_char(‘A’);wm_keyup+’a’;wm_keyup+shift

    Sorry, but without any code I cant say what you are doing wrong.

    regards

    Josef

  4. Raj says:

    I’ve just download this code and trying to run it in Windows7, VS2010 but it showing me an Error : “Unable to load coredll.dll.

    Please help me…

  5. admin says:

    The code will run on Windows Mobile up to version 6.5.3 and Pocket PC. It is not for Windows Phone or Desktop PC.

  6. andrea says:

    Hi,
    in the event handler, when a key is pressed:

    void HookEventProc(HookEventArgs hookArgs, KeyBoardInfo keyBoardInfo)
    {

    }

    is there a way to detect from hookargs members or keyboardInfo members if ctrl key was held down?
    I mean how can I detect for example the CTRL + 1 ckey combination?

    many thanks in advance

    regards
    Andrea

  7. admin says:

    Hello

    no, the modifier keys are not handled in a special way. In the event subscriber code you can watch for VK_CONTROL, VK_SHIFT, VK_LSHIFT and VK_ALT and other modifier keys and set flags when you get a WM_KEYDOWN for a modifier key and unset the flag, when you get a WM_KEYUP with a modifier VK_ value. HookEventArgs and KeyInfo transport all necessary info about the key.

    ~josef

  8. Jose Incera says:

    it works great on the first form, but when i try to use it on another form y shows “Failed acquiring of the hook”

  9. josef says:

    Hello Jose

    that is normal if you do not use that as “singleton”. Although MS makes us believe we could chain multiple hooks, it is not supported on Windows CE based devices.

    You have to ensure you install only one keyboard hook or unhook before you instantiate another keyboard hook.

    sorry

    Josef

  10. Mikael says:

    Hi

    I’m just trying this out.. However, i’m not successfull in getting this to stop the LWin buttons actions.

    I’ve tried converting it to a KeyEventArgs and set handled to true, but that did not help. The start menu of the device still pops open when the windows key is pressed on the keyboard.
    If i add a MessageBox, i can see that it recognizes the keypress, but doesn’t halt it.

    How should i do that?


    static void HookEventProc(HookEventArgs hookArgs, KeyBoardInfo keyBoardInfo)
    {

    if (vkMap.vkValues[keyBoardInfo.vkCode].s == "VK_LWIN")
    {
    Keys key = (Keys)keyBoardInfo.vkCode;
    KeyEventArgs kea = new KeyEventArgs(key);
    kea.Handled = true;
    MessageBox.Show("Key: " + kea.KeyCode.ToString());

    }

    }


    (This is for Windows CE, 6)
    BR
    Mikael

  11. josef says:

    Hello Mikael

    I did not test it in WinCE6 (no device here). But I have some comments:
    1. Do NOT call a blocking function like MessageBox.Show() in a event handler, here HookEventProc!
    2. You create a new KeyEventArgs. KeyEventArgs as used by you is OK for a keyevent handler, but HookEvent is not a managed KeyEventHandler as On_KeyPress, On_KeyDown/UP.
    3. To supress the key press to be signaled to the system you need to handle that in HookProcedure. You may add a “return TRUE;” for VK_LWIN instead of return “CallNextHookEx(hookDeleg, code, wParam, lParam);”. That way the key is not forwarded. The OnHookEvent(e, keyInfo); is to signal the key to managed code but the next line (return CallNextHookEx(hookDeleg, code, wParam, lParam);) forwards the pressed key to the next listener (the System).

    See also http://www.hjgode.de/wp/2012/09/20/windows-mobile-cf-how-to-catch-f1-and-f2-in-weh/

    regards

    Josef

  12. Josué says:

    Hi Josef
    First: your code did helpme a lot, so thank you so much
    Second: My english is very bad but i do my best
    Finally:
    How can i modify this code to let the listener hit some keys?
    a mean, just unhook some keys like volumeUP and volumeDOWN
    please help me again.

    Regards
    Josué

  13. josef says:

    Hello Josué

    the hook uses AllKeys(True) API and therefor the OS does not see F6/F7 (Volume Up/Down) any more. But you can watch for F6/F7 and if youre code sees that codes, it could fire VKEY_F6, VKEY_F7 using keybd_event API call. That will possibly handled by the OS.
    If the code starts to call itself using keybd_event, you need to set/reset a flag to avoid a loop.

    Within a hookproc in C++ I had an option to forward keys (CallNextHook…) that let some keys get back to the OS. That worked fine.

    ~josef

  14. Josué says:

    Thanks for responding,
    meaby im lost… please check my code.
    Just for now i am testing whith returno key

    //Para interceptar teclas propias del dispositivo
    public delegate void HookEvent(HookEventArgs hookArgs, KeyBoardInfo keyBoardInfo);

    //Para activar tecla volumenUP volumenDOWN
    [DllImport(“user32.dll”)]
    public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags,
    UIntPtr dwExtraInfo);

    const byte VK_RETURN = 0x0D;
    const uint KEYEVENTF_KEYUP = 0x0002;
    //

    static void HookEventProc(HookEventArgs hookArgs, KeyBoardInfo keyBoardInfo)
    {

    byte bScan = 0;
    uint eventKey = 0;
    UIntPtr otro = UIntPtr.Zero;

    if ((keyBoardInfo.vkCode == 117) || (keyBoardInfo.vkCode == 118)) {
    keybd_event(VK_RETURN, bScan, eventKey, otro);
    Thread.Sleep(100);
    keybd_event(VK_RETURN, bScan, KEYEVENTF_KEYUP, otro);
    }

    //System.Diagnostics.Debug.WriteLine(“Hook called”);
    string s = “0x” + HexEncoding.ToString(hookArgs.lParam.ToInt32());
    s += “\t0x” + HexEncoding.ToString(hookArgs.wParam.ToInt32());
    s += “\t0x” + HexEncoding.ToString(keyBoardInfo.scanCode);
    s += “\t0x” + HexEncoding.ToString(keyBoardInfo.flags);
    s += “\t0x” + HexEncoding.ToString(keyBoardInfo.vkCode);
    s += “\t” + vkMap.vkValues[keyBoardInfo.vkCode].s;
    //dbgprint(vkMap.vkValues[keyBoardInfo.vkCode].s);
    //dbgprint(s);
    //s += “\r\n”;
    //uui(s);

    }
    //

  15. josef says:

    Hello Josué

    what is the purpose of using hook for your application? What do you want to program?

    ~Josef

  16. Uli says:

    Hello Josef,

    I used this class to hook keyboard events in a c# software running in .NET Compact Framework 3.5 on a Windows CE 5.0 device and it works just fine in most cases.

    However, I figured a key press is not hooked anymore after the minimize button has been pressed in the top right corner of the window. Do you have any clue, what could be the reason for the keys not being recognized anymore?

    Great work though!

    Kind regards,
    Uli

    namespace HookKeys
    {
    public partial class Form1 : Form
    {
    public Form1()
    {
    InitializeComponent();

    // Inizialize keyboard hook event handler. Calls “HookEvent” method,
    // when any hardware button is pressed on the field computer.

    HookKeys hook = new HookKeys();
    hook.HookEvent += new HookKeys.HookEventHandler(HookEvent);
    hook.Start();
    }

    private void HookEvent(HookEventArgs e, KeyBoardInfo keyBoardInfo)
    {
    /*
    This method is called, when any hardware button on FC-200 is pressed.
    When the home button is pressed, foreground windows toggles between
    ReachConnectCE and TopSURV.
    */

    if (keyBoardInfo.vkCode == 36 && e.wParam.ToInt32() == 256) // Home button pressed
    {
    ToggleWindow();
    }
    }

    private void ToggleWindow()
    {
    // Bring window of other software to front
    }
    }
    }

  17. josef says:

    Hello Ulli

    I do not know the internals of SetWindowsHookEx but you try the following: Remove the Hook.Start() from Form_Load and move it to Forma_Activate. Add a Hook.Stop() to Form_Deactivate(). We are normally designing Kiosk Mode apps without the Minimize buttons. So this behavior was not seen before.

    If your intention is to get all key events before the system, you need to either use RegisterHotKey for all keys you want to get (even if the app is not started) or use a global keyboard hook (C/C++, see my iHook apps) that captures and redirects all keys.

    Please send a dteailed description of what you want to achieve, if you have further questions.

    Thanks

    Josef

  18. admin says:

    Hello Ulli

    please see the update of the article on how to use this with Activate/Deactivate.

    Have fun

    ~josef

Leave a Reply