Question

Currently I am working on a simple class library to handle global hot keys with the help of various blog posts and SO answers.

Consider this integral segment of code that I have put together.

protected override void WndProc(ref Message m)
{
    const int WM_HOTKEY = 0x0312;

    if (m.Msg == WM_HOTKEY)
    {
        var modifier = (int) m.LParam & 0xFFFF;
        var key = ((int) m.LParam & 0xFFFF);
    }

    base.WndProc(ref m);
}

Something that I really do not understand and would like explained how the bitmask works here. I have a reasonable understanding of how to apply bitwise operators but I do not understand why this specific bitmask is being applied here.

Additionally, I am struggling to understand the purpose of IntPtr. Why is LParam and IntPtr in this case? Thanks.

Was it helpful?

Solution

The documentation for WM_HOTKEY says that the LPARAM contains two 16 bit fields. The high word (higher 16 bits) has the VK of the key pressed, and the low word (lower 16 bits) has the modifiers specified.

LPARAM & 0xffff will get the low 16 bits, and LPARAM >> 16 will get the high 16 bits.

LPARAM & 0xffff works because the 32 bit LPARAM looks like:

   V bit 31                              V bit 0
   HIGH WORD           LOW WORD
0y vvvv vvvv vvvv vvvv mmmm mmmm mmmm mmmm where v is the vk, and m is the modifier 
0y 0000 0000 0000 0000 1111 1111 1111 1111 == 0x0000ffff
========================================== Bitwise and
0y 0000 0000 0000 0000 mmmm mmmm mmmm mmmm Just left with the modifier bits

LPARAM >> 16 works by shifting the bits 16 positions to the right

   V bit 31                              V bit 0
   HIGH WORD           LOW WORD
0y vvvv vvvv vvvv vvvv mmmm mmmm mmmm mmmm where v is the vk, and m is the modifier 
0y 0vvv vvvv vvvv vvvv vmmm mmmm mmmm mmmm right shifted once (>> 1)
0y 00vv vvvv vvvv vvvv vvmm mmmm mmmm mmmm right shifted twice (>> 2)
...
0y 0000 0000 0000 0000 vvvv vvvv vvvv vvvv right shifted sixteen times (>> 16)

LPARAM is defined as the size of a pointer, so it would map to an IntPtr in managed code. Since we only use the low 32 bits of the LPARAM, we need to chop of the high 32. We can cast to int to do so, but if you are running with checked arithmetic, it will result in an OverflowException. Instead, we cast the IntPtr to a long (which on a 32 bit machine will zero fill) then to a int (which will truncate).

If we do not resize the IntPtr to a Int32, you could get exceptions if an application were to send a message with high bits set as the result of the lparam >> 16.

IntPtr lparam = /* comes from WndProc */;
Int32 lparam32 = unchecked((int)(long)lparam);

Int16 lowWord = lparam32 & 0xffff;
Int16 highWord = lparam32 >> 16;

That being said, here is a full implementation of a hotkey listener in C# that I can't remember where I found. If anyone has attribution for this, please let me know, or edit this post to list the original author.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public sealed class KeyboardHook : IDisposable
{
    private int _currentId;
    private Window _window;

    public event EventHandler<KeyPressedEventArgs> KeyPressed;

    public KeyboardHook()
    {
        EventHandler<KeyPressedEventArgs> handler = null;
        this._window = new Window();
        if (handler == null)
        {
            handler = delegate (object sender, KeyPressedEventArgs args) {
                if (this.KeyPressed != null)
                {
                    this.KeyPressed(this, args);
                }
            };
        }
        this._window.KeyPressed += handler;
    }

    public void Dispose()
    {
        for (int i = this._currentId; i > 0; i--)
        {
            UnregisterHotKey(this._window.Handle, i);
        }
        this._window.Dispose();
    }

    public void RegisterHotKey(ModifierKeys modifier, Keys key)
    {
        this._currentId++;
        if (!RegisterHotKey(this._window.Handle, this._currentId, (uint) modifier, (uint) key))
        {
            throw new InvalidOperationException("Couldn’t register the hot key.");
        }
    }

    [DllImport("user32.dll")]
    private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
    [DllImport("user32.dll")]
    private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    private class Window : NativeWindow, IDisposable
    {
        private static int WM_HOTKEY = 0x312;

        public event EventHandler<KeyPressedEventArgs> KeyPressed;

        public Window()
        {
            this.CreateHandle(new CreateParams());
        }

        public void Dispose()
        {
            this.DestroyHandle();
        }

        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            if (m.Msg == WM_HOTKEY)
            {
                Keys key = ((Keys) (((int) m.LParam) >> 0x10)) & Keys.KeyCode;
                ModifierKeys modifier = ((ModifierKeys) ((int) m.LParam)) & ((ModifierKeys) 0xffff);
                if (this.KeyPressed != null)
                {
                    this.KeyPressed(this, new KeyPressedEventArgs(modifier, key));
                }
            }
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top