tags:

views:

78

answers:

1

Hello, I just wonder... I mainly use .NET but now I started to investigate WINAPI calls. For example I am using this piece of code to hook to the API functions. It starts freezing, when I try to debug the application...

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

public class Keyboard
{
    private const int WH_KEYBOARD_LL = 13;
    private const int WM_KEYDOWN = 0x0100;
    private static LowLevelKeyboardProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;
    public static event Action<Keys,bool, bool> KeyDown;

    public static void Hook()
    {
         _hookID = SetHook(_proc);           
    }

    public static void Unhook()
    {
        UnhookWindowsHookEx(_hookID);
    }

    private static IntPtr SetHook(LowLevelKeyboardProc proc)
    {
        using (Process curProcess = Process.GetCurrentProcess())
        using (ProcessModule curModule = curProcess.MainModule)
        {
            return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                GetModuleHandle(curModule.ModuleName), 0);
        }
    }

    private delegate IntPtr LowLevelKeyboardProc(
        int nCode, IntPtr wParam, IntPtr lParam);

    private static IntPtr HookCallback(
        int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            int vkCode = Marshal.ReadInt32(lParam);
            Keys k = (Keys) vkCode;
            if (KeyDown != null)
            {
                KeyDown.BeginInvoke(k, IsKeyPressed(VirtualKeyStates.VK_CONTROL),
                                    IsKeyPressed(VirtualKeyStates.VK_SHIFT),Callback,null);
            }
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

    public static void Callback(IAsyncResult result)
    {
       KeyDown.EndInvoke(result);
    }

    private static bool IsKeyPressed(VirtualKeyStates virtualKeyStates)
    {
        return (GetKeyState(virtualKeyStates) & (1 << 7))==128;
    }

    [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);

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

    [DllImport("user32.dll")]
    static extern short GetKeyState(VirtualKeyStates nVirtKey);
}
enum VirtualKeyStates : int
{
    VK_LBUTTON = 0x01,
    VK_RBUTTON = 0x02,
    VK_CANCEL = 0x03,
    VK_MBUTTON = 0x04,
    //
    VK_XBUTTON1 = 0x05,
    VK_XBUTTON2 = 0x06,
    //
    VK_BACK = 0x08,
    VK_TAB = 0x09,
    //
    VK_CLEAR = 0x0C,
    VK_RETURN = 0x0D,
    //
    VK_SHIFT = 0x10,
    VK_CONTROL = 0x11,
    VK_MENU = 0x12,
    VK_PAUSE = 0x13,
    VK_CAPITAL = 0x14,
    //
    VK_KANA = 0x15,
    VK_HANGEUL = 0x15,  /* old name - should be here for compatibility */
    VK_HANGUL = 0x15,
    VK_JUNJA = 0x17,
    VK_FINAL = 0x18,
    VK_HANJA = 0x19,
    VK_KANJI = 0x19,
    //
    VK_ESCAPE = 0x1B,
    //
    VK_CONVERT = 0x1C,
    VK_NONCONVERT = 0x1D,
    VK_ACCEPT = 0x1E,
    VK_MODECHANGE = 0x1F,
    //
    VK_SPACE = 0x20,
    VK_PRIOR = 0x21,
    VK_NEXT = 0x22,
    VK_END = 0x23,
    VK_HOME = 0x24,
    VK_LEFT = 0x25,
    VK_UP = 0x26,
    VK_RIGHT = 0x27,
    VK_DOWN = 0x28,
    VK_SELECT = 0x29,
    VK_PRINT = 0x2A,
    VK_EXECUTE = 0x2B,
    VK_SNAPSHOT = 0x2C,
    VK_INSERT = 0x2D,
    VK_DELETE = 0x2E,
    VK_HELP = 0x2F,
    //
    VK_LWIN = 0x5B,
    VK_RWIN = 0x5C,
    VK_APPS = 0x5D,
    //
    VK_SLEEP = 0x5F,
    //
    VK_NUMPAD0 = 0x60,
    VK_NUMPAD1 = 0x61,
    VK_NUMPAD2 = 0x62,
    VK_NUMPAD3 = 0x63,
    VK_NUMPAD4 = 0x64,
    VK_NUMPAD5 = 0x65,
    VK_NUMPAD6 = 0x66,
    VK_NUMPAD7 = 0x67,
    VK_NUMPAD8 = 0x68,
    VK_NUMPAD9 = 0x69,
    VK_MULTIPLY = 0x6A,
    VK_ADD = 0x6B,
    VK_SEPARATOR = 0x6C,
    VK_SUBTRACT = 0x6D,
    VK_DECIMAL = 0x6E,
    VK_DIVIDE = 0x6F,
    VK_F1 = 0x70,
    VK_F2 = 0x71,
    VK_F3 = 0x72,
    VK_F4 = 0x73,
    VK_F5 = 0x74,
    VK_F6 = 0x75,
    VK_F7 = 0x76,
    VK_F8 = 0x77,
    VK_F9 = 0x78,
    VK_F10 = 0x79,
    VK_F11 = 0x7A,
    VK_F12 = 0x7B,
    VK_F13 = 0x7C,
    VK_F14 = 0x7D,
    VK_F15 = 0x7E,
    VK_F16 = 0x7F,
    VK_F17 = 0x80,
    VK_F18 = 0x81,
    VK_F19 = 0x82,
    VK_F20 = 0x83,
    VK_F21 = 0x84,
    VK_F22 = 0x85,
    VK_F23 = 0x86,
    VK_F24 = 0x87,
    //
    VK_NUMLOCK = 0x90,
    VK_SCROLL = 0x91,
    //
    VK_OEM_NEC_EQUAL = 0x92,   // '=' key on numpad
    //
    VK_OEM_FJ_JISHO = 0x92,   // 'Dictionary' key
    VK_OEM_FJ_MASSHOU = 0x93,   // 'Unregister word' key
    VK_OEM_FJ_TOUROKU = 0x94,   // 'Register word' key
    VK_OEM_FJ_LOYA = 0x95,   // 'Left OYAYUBI' key
    VK_OEM_FJ_ROYA = 0x96,   // 'Right OYAYUBI' key
    //
    VK_LSHIFT = 0xA0,
    VK_RSHIFT = 0xA1,
    VK_LCONTROL = 0xA2,
    VK_RCONTROL = 0xA3,
    VK_LMENU = 0xA4,
    VK_RMENU = 0xA5,
    //
    VK_BROWSER_BACK = 0xA6,
    VK_BROWSER_FORWARD = 0xA7,
    VK_BROWSER_REFRESH = 0xA8,
    VK_BROWSER_STOP = 0xA9,
    VK_BROWSER_SEARCH = 0xAA,
    VK_BROWSER_FAVORITES = 0xAB,
    VK_BROWSER_HOME = 0xAC,
    //
    VK_VOLUME_MUTE = 0xAD,
    VK_VOLUME_DOWN = 0xAE,
    VK_VOLUME_UP = 0xAF,
    VK_MEDIA_NEXT_TRACK = 0xB0,
    VK_MEDIA_PREV_TRACK = 0xB1,
    VK_MEDIA_STOP = 0xB2,
    VK_MEDIA_PLAY_PAUSE = 0xB3,
    VK_LAUNCH_MAIL = 0xB4,
    VK_LAUNCH_MEDIA_SELECT = 0xB5,
    VK_LAUNCH_APP1 = 0xB6,
    VK_LAUNCH_APP2 = 0xB7,
    //
    VK_OEM_1 = 0xBA,   // ';:' for US
    VK_OEM_PLUS = 0xBB,   // '+' any country
    VK_OEM_COMMA = 0xBC,   // ',' any country
    VK_OEM_MINUS = 0xBD,   // '-' any country
    VK_OEM_PERIOD = 0xBE,   // '.' any country
    VK_OEM_2 = 0xBF,   // '/?' for US
    VK_OEM_3 = 0xC0,   // '`~' for US
    //
    VK_OEM_4 = 0xDB,  //  '[{' for US
    VK_OEM_5 = 0xDC,  //  '\|' for US
    VK_OEM_6 = 0xDD,  //  ']}' for US
    VK_OEM_7 = 0xDE,  //  ''"' for US
    VK_OEM_8 = 0xDF,
    //
    VK_OEM_AX = 0xE1,  //  'AX' key on Japanese AX kbd
    VK_OEM_102 = 0xE2,  //  "<>" or "\|" on RT 102-key kbd.
    VK_ICO_HELP = 0xE3,  //  Help key on ICO
    VK_ICO_00 = 0xE4,  //  00 key on ICO
    //
    VK_PROCESSKEY = 0xE5,
    //
    VK_ICO_CLEAR = 0xE6,
    //
    VK_PACKET = 0xE7,
    //
    VK_OEM_RESET = 0xE9,
    VK_OEM_JUMP = 0xEA,
    VK_OEM_PA1 = 0xEB,
    VK_OEM_PA2 = 0xEC,
    VK_OEM_PA3 = 0xED,
    VK_OEM_WSCTRL = 0xEE,
    VK_OEM_CUSEL = 0xEF,
    VK_OEM_ATTN = 0xF0,
    VK_OEM_FINISH = 0xF1,
    VK_OEM_COPY = 0xF2,
    VK_OEM_AUTO = 0xF3,
    VK_OEM_ENLW = 0xF4,
    VK_OEM_BACKTAB = 0xF5,
    //
    VK_ATTN = 0xF6,
    VK_CRSEL = 0xF7,
    VK_EXSEL = 0xF8,
    VK_EREOF = 0xF9,
    VK_PLAY = 0xFA,
    VK_ZOOM = 0xFB,
    VK_NONAME = 0xFC,
    VK_PA1 = 0xFD,
    VK_OEM_CLEAR = 0xFE
}

It works well even if you put messagebox into the event or something that blocks execution. But it gets bad if you try to put breakpoint into the event. Why? I mean event is not run in the same thread that the windows hook is. That means that It shouldn't block HookCallback. It does however...

I would really like to know why is this happening. My theory is that Visual Studio when breaking execution temporarily stops all threads and that means that HookCallback is blocked... Is there any book or valuable resource that would explain concepts behind all of this threading?

+2  A: 

Visual Studio really blocks all threads, including your hook thread. This is the pitfall. You can workaround this problem eg. by using a service, which will install the hook, so when you debug your application, the hook thread will not get blocked.

There is also another solution. In Windows registry is saved the maximum timeout for low level hook threads to respond ... if they don't respond in the time, they are "skipped". So you can change that value on your dev computer to say 250ms, and you should be fine to debug your app, since when you start debugging, the system will automatically start skipping your thread (well, not automatically, but in the 250ms). The registry key:

[HKEY_CURRENT USER\Control Panel\Desktop]

and value:

LowLevelHooksTimeout

The value is in milliseconds, so 3500 means 3.5 seconds (default is 5000 milliseconds).
Source:
http://unknownxpsecrets.blogspot.com/2009/03/set-low-level-hook-time-out-and-stop.html

Book about threading? Here your are:
http://www.albahari.com/threading/

Update

By the way, you are leaking memory - you must call EndInvoke for each call to BeginInvoke:
http://stackoverflow.com/questions/532722/is-endinvoke-optional-sort-of-optional-or-definitely-not-optional

Paja
Thanks, i fixed the memory leak and the LowLevelHooksTimeout helped me a lot :)
Kalaz