All posts in p/invoke

This is the first of a 2-part series on Accessing Monitor Information with C#.

The series will include the following:

  • Part 1: Getting Monitor Handles
  • Part 2: Getting a Monitor associated with a Window Handle

We will be discussing a number of components associated with Native Methods in these posts. If you are concerned about some references not being explicitly established in the demonstrated code, don’t worry: I have posted the source on CodePlex for your usage.

 
The Native Methods
I have stated this in earlier posts, but it bears repeating: “It has been accepted in the .Net community that you should use Native Methods only when the .Net Framework does not offer a managed solution. Manged is always better because it has been configured to be properly managed in memory, and to be recycled by the garbage collector. I tend to use Native Methods most often when I need to interact with an external process and there are no valid hooks in the Framework.

 
For this exercise, we will be using the following Native Methods and structures:

RECT
The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle.


/// <summary>
/// Rectangle
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

 
MONITORINFO
The MONITORINFO structure contains information about a display monitor. It is composed of the size of the structure, the RECT representing the area the application exists in the total viewable area (all monitors that make up the desktop space), the RECT representing the working area taken up by the application, and flags associated with special attributes assigned.


/// <summary>
/// Monitor information.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
    public uint size;
    public RECT monitor;
    public RECT work;
    public uint flags;
}

 
EnumDisplayMonitors
This method enumerates through the display monitors with the assistance of a delegate to process each monitor.


/// <summary>
/// Monitor Enum Delegate
/// </summary>
/// <param name="hMonitor">A handle to the display monitor.</param>
/// <param name="hdcMonitor">A handle to a device context.</param>
/// <param name="lprcMonitor">A pointer to a RECT structure.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the enumeration function.</param>
/// <returns></returns>
public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor,
    ref RECT lprcMonitor, IntPtr dwData);

/// <summary>
/// Enumerates through the display monitors.
/// </summary>
/// <param name="hdc">A handle to a display device context that defines the visible region of interest.</param>
/// <param name="lprcClip">A pointer to a RECT structure that specifies a clipping rectangle.</param>
/// <param name="lpfnEnum">A pointer to a MonitorEnumProc application-defined callback function.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the MonitorEnumProc function.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip,
	MonitorEnumDelegate lpfnEnum, IntPtr dwData);

 
GetMonitorInfo
Retrieves the monitor information.

	
/// <summary>
/// Gets the monitor information.
/// </summary>
/// <param name="hmon">A handle to the display monitor of interest.</param>
/// <param name="mi">A pointer to a MONITORINFO instance created by this method.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool GetMonitorInfo(IntPtr hmon, ref MONITORINFO mi);

 
Next, we need to define MonitorInfoWithHandle, which will store the MONITORINFO and monitor handle for each monitor instance:


/// <summary>
/// Monitor information with handle.
/// </summary>
public class MonitorInfoWithHandle : IMonitorInfoWithHandle
{
    /// <summary>
    /// Gets the monitor handle.
    /// </summary>
    /// <value>
    /// The monitor handle.
    /// </value>
    public IntPtr MonitorHandle { get; private set; }

    /// <summary>
    /// Gets the monitor information.
    /// </summary>
    /// <value>
    /// The monitor information.
    /// </value>
    public MONITORINFO MonitorInfo { get; private set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="MonitorInfoWithHandle"/> class.
    /// </summary>
    /// <param name="monitorHandle">The monitor handle.</param>
    /// <param name="monitorInfo">The monitor information.</param>
    public MonitorInfoWithHandle(IntPtr monitorHandle, MONITORINFO monitorInfo)
    {
        MonitorHandle = monitorHandle;
        MonitorInfo = monitorInfo;
    }
}

 
Now that we have our Native Methods and structures in place, let’s build our explicit MonitorEnumProc which will be used by our enumeration through the monitors:


/// <summary>
/// Monitor Enum Delegate
/// </summary>
/// <param name="hMonitor">A handle to the display monitor.</param>
/// <param name="hdcMonitor">A handle to a device context.</param>
/// <param name="lprcMonitor">A pointer to a RECT structure.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the enumeration function.</param>
/// <returns></returns>
public static bool MonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData)
{
    var mi = new MONITORINFO();
    mi.size = (uint)Marshal.SizeOf(mi);
    NativeMonitorHelper.GetMonitorInfo(hMonitor, ref mi);

    // Add to monitor info
    _monitorInfos.Add(new MonitorInfoWithHandle(hMonitor, mi));
    return true;
}

Here is what this is doing:

  1. A new MONITORINFO structure is instantiated for the purpose of containing the data discovered by GetMonitorInfo.
  2. The size of the MONITORINFO is determined as a pre-requisite for the instance to be properly populated. Note: We see this a lot with structures used with Native Methods.
  3. GetMonitorInfo is called with a monitor handle provided by EnumDisplayMonitors, as well as our newly created MONITORINFO instance.
  4. A new MonitorInfoWithHandle instance is created with the provided monitor handle and our newly populated MONITORINFO instance, which is then added to a persisted list.

 
Lastly, we need to put together our EnumDisplayMonitors method which will enumerate through all the display monitors using our MonitorEnum delegate:


/// <summary>
/// Gets the monitors.
/// </summary>
/// <returns></returns>
public static MonitorInfoWithHandle[] GetMonitors()
{
    // New List
    _monitorInfos = new List<MonitorInfoWithHandle>();

    // Enumerate monitors
    NativeMonitorHelper.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero);

    // Return list
    return _monitorInfos.ToArray();
}

What this does is a lot simpler since the majority of the work is done by MonitorEnum:

  1. The monitor info list is created.
  2. EnumDisplayMonitors is called with our MonitorEnum delegate to retrieve the data for each monitor.
  3. The monitor info array is returned to the caller.

 
And that’s all there is to it. There are a lot of components but it’s actually pretty straight forward.

Next time in Part 2, we will look at how we can discover what monitor a specific process is displayed when it has a valid Main Window Handle.

Until then…

It’s been a little while since we last hung out, but I think it’s time for some more informative articles on crazy stuff some are afraid to try at home. This week we are going to look at reacting to when Alt+Tab is pressed in our WPF applications. To do this, we will be using the Native Methods in Windows via p/invoke as we have in previous articles.

 
The Native Methods
It has been accepted in the .Net community that you should use Native Methods only when the .Net Framework does not offer a managed solution. Manged is always better because it has been configured to be properly managed in memory, and to be recycled by the garbage collector. I tend to use Native Methods most often when I need to interact with an external process and there are no valid hooks in the Framework. This is one of those examples.

So, for today’s example we will use the following Native Methods:

SetWindowsHookEx
This will set our application procedure into a hook chain. Why a chain? Because we are listening for certain messages that relate to our key presses, but we are not attempting to replace what is already there.

For our example, we will be using WH_KEYBOARD_LL since we will be interacting with low-level keyboard events.

Additionally, we will need a low-level keyboard processor (LowLevelKeyboardProc) to carry out the work we want to perform, which consists of a code number, the keyboard action taken, and a struct (KBDLLHOOKSTRUCT) which allows us to get additional information about our keyboard action.

CallNextHookEx
Passes the hook information to the next procedure in the hook chain. We should make a habit of calling this after we do the work in our hook procedure to ensure the next procedures in the hook chain are properly invoked. It’s still a bit controversial if this is still needed, but the general opinion of our peers is that it should still be used.

GetKeyState
Gets the state of a provided virtual key. This will allow us to determine if the desired keys have been pressed.

UnhookWindowsHookEx
Removes a hook procedure from a hook chain. We will use this method to cleanup when we are finished with our work.

 
Okay, not so bad, right?

So, for this example we will add the code we need to App.xaml.cs. To start, here are our native methods that correspond with what we stated above:


/// <summary>
/// Hook Processor.
/// </summary>
/// <param name="code">The code.</param>
/// <param name="wParam">The w parameter.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

/// <summary>
/// Sets a windows hook.
/// </summary>
/// <param name="hookId">The hook identifier.</param>
/// <param name="processor">The processor.</param>
/// <param name="moduleInstance">The module instance.</param>
/// <param name="threadId">The thread identifier.</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int hookId, HookProc processor, IntPtr moduleInstance, int threadId);

/// <summary>
/// Unsets a windows hook.
/// </summary>
/// <param name="hookId">The hook identifier.</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int hookId);

/// <summary>
/// Calls the next hook.
/// </summary>
/// <param name="hookId">The hook identifier.</param>
/// <param name="code">The code.</param>
/// <param name="wParam">The w parameter.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int hookId, int code, IntPtr wParam, IntPtr lParam);

/// <summary>
/// Gets the state of a virtual key.
/// </summary>
/// <param name="virtualKey">The virtual key.</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern short GetKeyState(int virtualKey);

 
Note: we can use HookProc for all of our processors because this is the standard delegate for interacting with SetWindowsHookEx. The key to getting information indigenous to a specific hook type is by casting the lParam parameter to the expected structure type. We’ll talk more about that below.

 
Next, we need to make sure we define our fields:


/// <summary>
/// Event hook types
/// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644959%28v=vs.85%29.aspx
/// </summary>
public static class EventHookTypes
{
    public static readonly int WH_MSGFILTER = -1;
    public static readonly int WH_JOURNALRECORD = 0;
    public static readonly int WH_JOURNALPLAYBACK = 1;
    public static readonly int WH_KEYBOARD = 2;
    public static readonly int WH_GETMESSAGE = 3;
    public static readonly int WH_CALLWNDPROC = 4;
    public static readonly int WH_CBT = 5;
    public static readonly int WH_SYSMSGFILTER = 6;
    public static readonly int WH_MOUSE = 7;
    public static readonly int WH_HARDWARE = 8;
    public static readonly int WH_DEBUG = 9;
    public static readonly int WH_SHELL = 10;
    public static readonly int WH_FOREGROUNDIDLE = 11;
    public static readonly int WH_CALLWNDPROCRET = 12;
    public static readonly int WH_KEYBOARD_LL = 13;
    public static readonly int WH_MOUSE_LL = 14;
}

// Key states
const int KEYSTATE_NONE = 0;
const int KEYSTATE_NOTPRESSED_TOGGLED = 1;
const int KEYSTATE_PRESSED_TOGGLED = -128;
const int KEYSTATE_PRESSED_NOT_TOGGLED = -127;

/// <summary>
/// Contains information about a low-level keyboard input event.
/// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644967%28v=vs.85%29.aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct KBLLHOOKSTRUCT
{
    public int KeyCode;
    public int ScanCode;
    public int Flags;
    public int Time;
    public int ExtraInfo;
}

private static int _hookHandle;
private static HookProc _altTabCallBack;

 
The event hook types represent all possible event hook types used by SetWindowsHookEx. Given, we don’t need more than one for this example but I wanted to give you the entire picture so you leave here looking to see how deep this rabbit hole goes.

The key states represent the different key states we want to track. There are different states for buttons that serve as toggles versus standard buttons.

KBLLHOOKSTRUCT is our expected structure which we will obtain by casting the lParam in our HookProc.

Lastly, we have private members for the hook handle, and the HookProc to ensure the Garbage Collector does not dispose of these prematurely. Remember, Native Methods are unmanaged, and without the proper references Garbage Collector will assume they are unused and collect them as soon as it checks. This can lead to a fun exception “A callback was made on a garbage collected delegate of type…” which I will talk about in a future post.

 
Creating the Processor

So, now that we have established the Native Methods and the private members it’s time to get to the heart of our code:


/// <summary>
/// Sets a keyboard Alt-Tab hook.
/// </summary>
private static void SetAltTabHook()
{
    // Set reference to callback
    _altTabCallBack = AltTabProcessor;

    // Set system-wide hook.
    _hookHandle = SetWindowsHookEx(EventHookTypes.WH_KEYBOARD_LL,
        _altTabCallBack, IntPtr.Zero, 0);
}

/// <summary>
/// The processor for Alt+Tab key presses.
/// </summary>
/// <param name="code">The code.</param>
/// <param name="wParam">The w parameter.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
private static int AltTabProcessor(int code, IntPtr wParam, IntPtr lParam)
{
    const int altKey = 0x12;
    const int tabKey = 0x09;

    // Evaluate
    if (code >= 0)
    {
        var hookStruct = (KBLLHOOKSTRUCT)Marshal.PtrToStructure(
            lParam, typeof(KBLLHOOKSTRUCT));

        // Determine if the alt key is pressed
        bool isAltKeyDown = 
            GetKeyState(altKey) == KEYSTATE_PRESSED_NOT_TOGGLED ||
            GetKeyState(altKey) == KEYSTATE_PRESSED_TOGGLED;

        // In addition to the alt key being pressed, check if TAB is also pressed
        if (isAltKeyDown && hookStruct.KeyCode == tabKey)
        {
            // Do stuff when Alt+Tab is pressed
        }

        else
        {
            // Do stuff when Alt+Tab is released
        }
    }

    // Pass to other keyboard handlers. This allows other applications with hooks to 
    // get the notification.
    return CallNextHookEx(_hookHandle, code, wParam, lParam);
}

 
SetAltTabHook does 3 things:

  1. Sets a reference to our AltTabProcessor to ensure it’s not Garbage Collected.
  2. Uses SetWindowsHookEx to set the windows hook with a type of WH_KEYBOARD_LL and our AltTabProcessor for the local application.
  3. Stores the returned hook handle.

AltTabProcessor is our HookProc designed specifically to manage our Alt+Tab check. What this does:

  1. Checks to ensure the code returned is greater than or equal to HC_ACTION (0).
  2. Casts lParam to a structure of type KBLLHOOKSTRUCT to expose additional information about what was processed.
  3. Uses GetKeyState on our Alt key to ensure it has been pressed or toggled.
  4. Checks that in addition to the Alt key being pressed, that the KBLLHOOKSTRUCT.KeyCode is equal to the Tab key.
  5. Relays the hook information to the next hook procedure in the hook chain with CallNextHookEx.

 
Putting it all together

Our complete App.xaml.cs:


using System;
using System.Runtime.InteropServices;
using System.Windows;

namespace WpfAltTabExample
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        #region Members

        /// <summary>
        /// Event hook types
        /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644959%28v=vs.85%29.aspx
        /// </summary>
        public static class EventHookTypes
        {
            public static readonly int WH_MSGFILTER = -1;
            public static readonly int WH_JOURNALRECORD = 0;
            public static readonly int WH_JOURNALPLAYBACK = 1;
            public static readonly int WH_KEYBOARD = 2;
            public static readonly int WH_GETMESSAGE = 3;
            public static readonly int WH_CALLWNDPROC = 4;
            public static readonly int WH_CBT = 5;
            public static readonly int WH_SYSMSGFILTER = 6;
            public static readonly int WH_MOUSE = 7;
            public static readonly int WH_HARDWARE = 8;
            public static readonly int WH_DEBUG = 9;
            public static readonly int WH_SHELL = 10;
            public static readonly int WH_FOREGROUNDIDLE = 11;
            public static readonly int WH_CALLWNDPROCRET = 12;
            public static readonly int WH_KEYBOARD_LL = 13;
            public static readonly int WH_MOUSE_LL = 14;
        }

        // Key states
        const int KEYSTATE_NONE = 0;
        const int KEYSTATE_NOTPRESSED_TOGGLED = 1;
        const int KEYSTATE_PRESSED_TOGGLED = -128;
        const int KEYSTATE_PRESSED_NOT_TOGGLED = -127;

        /// <summary>
        /// Contains information about a low-level keyboard input event.
        /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644967%28v=vs.85%29.aspx
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct KBLLHOOKSTRUCT
        {
            public int KeyCode;
            public int ScanCode;
            public int Flags;
            public int Time;
            public int ExtraInfo;
        }

        private static int _hookHandle;
        private static HookProc _altTabCallBack;

        #endregion

        #region Methods

        /// <summary>
        /// Sets a keyboard Alt+Tab hook.
        /// </summary>
        private static void SetAltTabHook()
        {
            // Set reference to callback
            _altTabCallBack = AltTabProcessor;

            // Set system-wide hook.
            _hookHandle = SetWindowsHookEx(EventHookTypes.WH_KEYBOARD_LL,
                _altTabCallBack, IntPtr.Zero, 0);
        }

        /// <summary>
        /// The processor for Alt+Tab key presses.
        /// </summary>
        /// <param name="code">The code.</param>
        /// <param name="wParam">The w parameter.</param>
        /// <param name="lParam">The l parameter.</param>
        /// <returns></returns>
        private static int AltTabProcessor(int code, IntPtr wParam, IntPtr lParam)
        {
            const int altKey = 0x12;
            const int tabKey = 0x09;

            // Evaluate
            if (code >= 0)
            {
                var hookStruct = (KBLLHOOKSTRUCT)Marshal.PtrToStructure(
                    lParam, typeof(KBLLHOOKSTRUCT));

                // Determine if the alt key is pressed
                bool isAltKeyDown = 
                    GetKeyState(altKey) == KEYSTATE_PRESSED_NOT_TOGGLED ||
                    GetKeyState(altKey) == KEYSTATE_PRESSED_TOGGLED;

                // In addition to the alt key being pressed, check if TAB is also pressed
                if (isAltKeyDown && hookStruct.KeyCode == tabKey)
                {
                    // Do stuff when Alt+Tab is pressed
                }

                else
                {
                    // Do stuff when Alt+Tab is released
                }
            }

            // Pass to other keyboard handlers. This allows other applications with hooks to 
            // get the notification.
            return CallNextHookEx(_hookHandle, code, wParam, lParam);
        }

        #endregion

        #region Events

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Application.Startup" /> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.StartupEventArgs" /> that contains the 
        /// event data.</param>
        protected override void OnStartup(StartupEventArgs e)
        {
            // Startup
            SetAltTabHook();

            // Base
            base.OnStartup(e);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Application.Exit" /> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.Windows.ExitEventArgs" /> that contains the 
        /// event data.</param>
        protected override void OnExit(ExitEventArgs e)
        {
            // Unhook
            UnhookWindowsHookEx(_hookHandle);

            // Base
            base.OnExit(e);
        }

        #endregion

        #region Native Methods

        /// <summary>
        /// Hook Processor.
        /// </summary>
        /// <param name="code">The code.</param>
        /// <param name="wParam">The w parameter.</param>
        /// <param name="lParam">The l parameter.</param>
        /// <returns></returns>
        public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

        /// <summary>
        /// Sets a windows hook.
        /// </summary>
        /// <param name="hookId">The hook identifier.</param>
        /// <param name="processor">The processor.</param>
        /// <param name="moduleInstance">The module instance.</param>
        /// <param name="threadId">The thread identifier.</param>
        /// <returns></returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int hookId, HookProc processor, IntPtr moduleInstance, int threadId);

        /// <summary>
        /// Unsets a windows hook.
        /// </summary>
        /// <param name="hookId">The hook identifier.</param>
        /// <returns></returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int hookId);

        /// <summary>
        /// Calls the next hook.
        /// </summary>
        /// <param name="hookId">The hook identifier.</param>
        /// <param name="code">The code.</param>
        /// <param name="wParam">The w parameter.</param>
        /// <param name="lParam">The l parameter.</param>
        /// <returns></returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(int hookId, int code, IntPtr wParam, IntPtr lParam);

        /// <summary>
        /// Gets the state of a virtual key.
        /// </summary>
        /// <param name="virtualKey">The virtual key.</param>
        /// <returns></returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern short GetKeyState(int virtualKey);

        #endregion
    }
}

 
Final Notes:
Notice we call UnhookWindowsHookEx(_hookHandle) in OnExit. This is done to responsibly remove our hook processor from its hook chain. You should always plan on performing cleanup actions on unmanaged code, otherwise you can’t guarantee it will get released from memory and that’s just plain sloppy.

We are far from done with Native Methods, so stay tuned!

Happy Coding.