Search code examples
c#.netinteroppinvoke

TranslateAccelerator succeeds, but WM_COMMAND messages are not showing up


I am writing a .NET wrapper around Win32 hooks which buffers WM_CHAR messages and allows events such as key presses, key releases, and accelerator keystrokes to be subscribed to. Everything is in working order except apparently my call to TranslateAccelerator. It returns true when I expect it to (when it finds an accelerator in the given table which I constructed earlier) but the WM_COMMAND messages don't seem to be showing up ever. Here is some relevant code.

[StructLayout(LayoutKind.Sequential)]
struct MSG {

    IntPtr hWnd;

    WindowsMessages message;

    IntPtr wParam;

    IntPtr lParam;

    UInt32 time;

    POINT pt;
}

delegate IntPtr HOOKPROC(HookCodes nCode, IntPtr wParam, ref MSG lParam);

[DllImport("user32.dll")] 
extern IntPtr CallNextHookEx(IntPtr hhk, HookCodes nCode, IntPtr wParam, ref MSG lParam);

IntPtr HookProcedure(HookCodes nCode, IntPtr wParam, ref MSG lParam) {
    IntPtr result = IntPtr.Zero;
    if(nCode < HookCodes.ACTION) { 
        result = CallNextHookEx(hHook, nCode, wParam, ref lParam);
    } else if(nCode == HookCodes.ACTION && (PeekMessageOptions)wParam == PeekMessageOptions.REMOVE) {

        /*
         * Under these conditions, each message will only be passed onto the switch below once.
         */

        switch(lParam.message) {
            case WindowsMessages.KEYDOWN: 
                //fire keydown events and call TranslateAccelerator/TranslateMessage
                break;
            case WindowsMessages.KEYUP: 
                //fire keyup events
                break;
            case WindowsMessages.COMMAND: 
                //fire accelerator events Ex: Ctrl+F, ALT+M, SHIFT+L
                break;
            case WindowsMessages.CHAR: 
                //place char in buffer
                break;
            default:
                break;
            }
        }
        return result; //Will be zero if action was taken on the message by this procedure.  
    }
}

In KeyPressed(MSG), messages are translated like so:

[DllImport("user32.dll")]
extern bool TranslateAccelerator(IntPtr hWnd, IntPtr hAccTable, ref MSG lpMsg);

[DllImport("user32.dll")] 
extern bool TranslateMessage(ref MSG lpMsg);

if(!TranslateAccelerator(hWnd, hAccel, ref msg)){
    TranslateMessage(ref msg);
}

The hook and accelerator table are created like this:

[StructLayout(LayoutKind.Sequential)]
struct ACCEL {

    byte fVirt;

    ushort key;

    ushort cmd;
}

[DllImport("user32.dll")] 
extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);

[DllImport("user32.dll")] 
extern IntPtr SetWindowsHookEx(WindowsHooks hook, HookProcedure lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll")]
extern IntPtr CreateAcceleratorTable(ACCEL[] lpaccl, int cEntries);

HOOKPROC proc = HookProcedure;
uint pid = GetWindowThreadProcessId(/*IntPtr*/hWnd, IntPtr.Zero);
IntPtr hHook = SetWindowsHookEx(WindowsHooks.GETMESSAGE, proc, IntPtr.Zero, pid);
IntPtr hAccel = CreateAcceleratorTable(/*ACCEL[]*/accelerators, accelerators.Length);

Everything works fine (such as attaching events to key-downs and buffering WM_CHAR messages) except I can't find WM_COMMAND or WM_SYSCOMMAND messages anywhere in the queue. They just don't seem to be visible to my hook procedure, even though TranslateAccelerator returns true when I expect it to. Note that I am really pretty clueless when it comes to Win32 so I'm probably missing something fairly obvious to the trained eye. But I am at my wits end. I've tried attaching the hook to an XNA window and a Windows Form without success.


Solution

  • You won't catch those WM_COMMAND messages with a GETMESSAGE hook.

    TranslateAccelerator "sends the WM_COMMAND or WM_SYSCOMMAND message directly to the specified window procedure" (see the documentation) and hence bypasses the message queue.

    You'll need a CALLWNDPROC hook instead.