Using the Windows API to emulate hardware input in C#

Posted in software by Christopher R. Wirz on Wed Apr 13 2016



C# is managed code, but parts of the Windows API were built before the indroduction of C#. Fortunately, there are InteropServices to call the API and tools (such as PInvoke) expose the method signatures and compliant types.

A good demonstration of this principle is mouse movement. Mouse movement, when accomplished through the Windows API, emulates hardware interaction. This allows you to do things like keep your test machine from running the screen saver, replicate precise procedures, or prototype custom hardware before writing a driver.

The DllImport attribute is very useful when reusing existing unmanaged code when a managed application might need to make calls to the unmanaged WIN32 API.


using System;
using System.Runtime.InteropServices;

namespace Common
{
    public class User32
    {
        [DllImport("user32.dll")]
        public static extern long SetCursorPos(int x, int y);

        [DllImport("user32.dll")]
        public static extern bool ClientToScreen(IntPtr hWnd, ref POINT point);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetCursorPos(out POINT p);
        
        [DllImport("user32.dll", SetLastError = true)]
        public static extern uint SendInput(uint nInputs, ref INPUT pInputs, int cbSize);

        [DllImport("user32.dll")]
        public static extern IntPtr GetDesktopWindow();

        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowDC(IntPtr ptr);
    }
}

Based on the parameter values, this line of code tells the compiler to declare a function residing in the User32.dll and to treat all strings appearing in the signature (such as parameters or the return value) like Unicode strings. If the EntryPoint argument is missing, the default value is the name of the function.

Note: When calling a function contained in a user-defined DLL, it is necessary to precede the DLL function declaration with extern "C", not just extern. For example, if user32custom.dll was a custom DLL, the signature would be written as
	
[DllImport("user32custom.dll")]
public static extern "C" IntPtr GetWindowDC(IntPtr ptr);
	
	

Now that the method signatures are defined, compatible inputs must be created as well if the methods are to be used. This sometimes involves a bit of research depending on the DLL you are trying to import. Tools like PInvoke may help. When creating the types, they will require attributes so the compiler knows how to export them to the unmanaged code.

The LayoutKind.Sequential attribute controls the layout of the object when exported to unmanaged code. The LayoutKind.Explicit controls precise position of each member of an object in unmanaged memory. Each member must use the FieldOffset attribute to indicate the position of that field within the type. The [Flags] attribute indicates that the enumeration can be treated as a bit field.


using System;
using System.Runtime.InteropServices;

namespace Common
{
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int x;
        public int y;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct INPUT
    {
        public SendInputEventType type;
        public MOUSEANDKEYBOARDINPUT mkhi;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct KEYBOARDINPUT
    {
        public ushort wVk;
        public ushort wScan;
        public uint dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct HARDWAREINPUT
    {
        public int uMsg;
        public short wParamL;
        public short wParamH;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MOUSEINPUT
    {
        public int dx;
        public int dy;
        public uint mouseData;
        public MouseEventFlags dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct MOUSEANDKEYBOARDINPUT
    {
        [FieldOffset(0)]
        public MOUSEINPUT mi;

        [FieldOffset(0)]
        public KEYBOARDINPUT ki;

        [FieldOffset(0)]
        public HARDWAREINPUT hi;
    }
	
    [Flags]
    public enum MouseEventFlags : uint
    {
        MOUSEEVENT_MOVE = 0x0001,
        MOUSEEVENT_LEFTDOWN = 0x0002,
        MOUSEEVENT_LEFTUP = 0x0004,
        MOUSEEVENT_RIGHTDOWN = 0x0008,
        MOUSEEVENT_RIGHTUP = 0x0010,
        MOUSEEVENT_MIDDLEDOWN = 0x0020,
        MOUSEEVENT_MIDDLEUP = 0x0040,
        MOUSEEVENT_XDOWN = 0x0080,
        MOUSEEVENT_XUP = 0x0100,
        MOUSEEVENT_WHEEL = 0x0800,
        MOUSEEVENT_VIRTUALDESK = 0x4000,
        MOUSEEVENT_ABSOLUTE = 0x8000
    }

    [Flags]
    public enum SendInputEventType : uint
    {
        InputMouse,
        InputKeyboard,
        InputHardware
    }
}

Once the types have been created, the methods and types can be incorporated in the C# code.


using System.Runtime.InteropServices;

namespace Common
{
    public class MouseSimulator
    {
        public static void ClickLeftMouseButton()
        {
            INPUT mouseDownInput = new INPUT();
            mouseDownInput.type = SendInputEventType.InputMouse;
            mouseDownInput.mkhi.mi.dwFlags = MouseEventFlags.MOUSEEVENT_LEFTDOWN;
            User32.SendInput(1, ref mouseDownInput, Marshal.SizeOf(new INPUT()));

            INPUT mouseUpInput = new INPUT();
            mouseUpInput.type = SendInputEventType.InputMouse;
            mouseUpInput.mkhi.mi.dwFlags = MouseEventFlags.MOUSEEVENT_LEFTUP;
            User32.SendInput(1, ref mouseUpInput, Marshal.SizeOf(new INPUT()));
        }
        public static void ClickRightMouseButton()
        {
            INPUT mouseDownInput = new INPUT();
            mouseDownInput.type = SendInputEventType.InputMouse;
            mouseDownInput.mkhi.mi.dwFlags = MouseEventFlags.MOUSEEVENT_RIGHTDOWN;
            User32.SendInput(1, ref mouseDownInput, Marshal.SizeOf(new INPUT()));

            INPUT mouseUpInput = new INPUT();
            mouseUpInput.type = SendInputEventType.InputMouse;
            mouseUpInput.mkhi.mi.dwFlags = MouseEventFlags.MOUSEEVENT_RIGHTUP;
            User32.SendInput(1, ref mouseUpInput, Marshal.SizeOf(new INPUT()));
        }

        public static void MouseMove(int dx, int dy)
        {
            INPUT mouseMove = new INPUT();
            mouseMove.type = SendInputEventType.InputMouse;
            mouseMove.mkhi.mi.dwFlags = MouseEventFlags.MOUSEEVENT_MOVE;
            mouseMove.mkhi.mi.dx = dx;
            mouseMove.mkhi.mi.dy = dy;
            User32.SendInput(1, ref mouseMove, Marshal.SizeOf(new INPUT()));
        }
    }
}

This example has shown a way to use the Windows API to drive mouse movement in C#. You can create many more methods using this approach. Of course, you will probably want to get the size of the screen (the windows API is not necessarily required for this).


double maxHeight = System.Windows.SystemParameters.PrimaryScreenHeight;
double maxWidth = System.Windows.SystemParameters.PrimaryScreenWidth;

This next snipplet will help to find the current mouse position.


POINT start;
User32.GetCursorPos(out start);
int x = start.x;
int y = start.y;

Looking for a job?