views:

45

answers:

3

My application should perform some action whenever user pressed certain keys in windows.

Calling SetWindowsHookEx with WH_KEYBOARD_LL option seems to be standard way to achieve this. However in my case something is clearly wrong and callback in not fired.

Main method of my debugging console application:

static void Main(string[] args)
{
    IntPtr moduleHandle = GetCurrentModuleHandle();
    IntPtr hookHandle = IntPtr.Zero;

    try
    {
        User32.HookProc hook = (nCode, wParam, lParam) =>
        {
            // code is never called :-(
            if (nCode >= 0)
            {
                Console.WriteLine("{0}, {1}", wParam.ToInt32(), lParam.ToInt32());
            }
            return User32.CallNextHookEx(hookHandle, nCode, wParam, lParam);
        };

        hookHandle = User32.SetWindowsHookEx(User32.WH_KEYBOARD_LL, hook, moduleHandle, 0);

        Console.ReadLine(); // 
    }
    finally
    {
        if (hoodHandle != IntPtr.Zero)
        {
            var unhooked = User32.UnhookWindowsHookEx(hookHandle);
            Console.WriteLine(unhooked); // true
            hookHandle = IntPtr.Zero;                   
        }
    }
}

GetCurrentModuleHandle method:

private static IntPtr GetCurrentModuleHandle()
{
    using (var currentProcess = Process.GetCurrentProcess())
    using (var mainModule = currentProcess.MainModule)
    {
        var moduleName = mainModule.ModuleName;
        return Kernel32.GetModuleHandle(moduleName);
    }           
}

Imports from user32.dll and kernel32.dll:

public static class User32
{
    public const int WH_KEYBOARD_LL = 13;

    public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);        
}

public static class Kernel32
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr GetModuleHandle(string lpModuleName);
}

Do you have any idea what I is my problem?

+2  A: 

The SetwindowHook API cannot be called from a C# console application. You need to call it in a Windows DLL (it won't work in a .Net DLL).

There is certainly a way to work around this issue, and I did use it one of the applications I built a long while ago. But I don't remember it right now. You may be able to find out if you search long enough.

Cyril Gupta
@Cyril Gupta: Are you sure? It seems to work for others. http://stackoverflow.com/questions/tagged/setwindowshookex+c%23
Jakub Šturc
+1  A: 

When you use the Win32 API like this, it is very important that you check for errors yourself. You no longer have the friendly .NET wrappers that will do it for you and throw an exception. This needs to be done in several places, but here's one:

    hookHandle = User32.SetWindowsHookEx(User32.WH_KEYBOARD_LL, hook, moduleHandle, 0);
    if (hookHandle == IntPtr.Zero) throw new Win32Exception();

The real problem is your use of mainModule.ModuleName. If only gives the name of the file, not the full path. GetModuleHandle() will fail and return IntPtr.Zero. As above, if you would have tested this then you'd have quickly found the problem.

There's an additional failure mode when you run this code in .NET 4.0. The CLR no longer fakes native modules for managed code. SetWindowsHookEx() needs a valid DLL handle but it doesn't actually use it since this is a low-level hook. The best way to get one is to ask for user32.dll, it is always loaded in a managed program:

    IntPtr moduleHandle = LoadLibrary("user32.dll");
    if (moduleHandle == IntPtr.Zero) throw new Win32Exception();

No need to release it.

Hans Passant
@Hans Passant: Thanks for answer. You are right I should always check return value of native calls. However this isn't issue. `UnhookWindowsHookEx` returns `true` so clearly callback was hooked.
Jakub Šturc
+1  A: 

Is this a console app as Main(string[] args) and Console.ReadLine() would suggest?

if so then this might be the source of your problem

Conrad Frix