Posts Tagged ‘USB’

First off, what the fork is a Human Interface Device? Well, you are potentially using two right now, your keyboard and mouse. When referring to an HID we are usually speaking of a USB-HID, or a device connected to your PC that supports bi-directional communication. Wait, how does your keyboard support bi-directional communication? Well, when Caps Lock isn’t there a light to notify you of that? In fact, on your Windows OS, if you go to Start > Control Panel > Device Manager, and expand the Keyboards Node, there is a good chance you’ll see a HID Keyboard Device listed. While there, expand the Human Interface Devices node to see what other HIDs are currently connected if any. For more general information on Human Interface Devices have a look here.

Now, if you’ve ever written an application that requires to you to communicate with one of these HIDs, then you probably have to go find the correct target device first. I’m going to show you how to do that in the following sample. First, we need a class to give us access to functions like SetupDiGetDeviceInterfaceDetail, SetupDiEnumDeviceInterfaces, and a few others. Much of the functionality in the following class, and the others, originated from a nice codeplex project here. Now for the HIDImports.cs code.

//	HIDImports.cs
//	For more information: http://wiimotelib.codeplex.com/
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace CreateBinaryFromXml
        public class HIDImports
            //Flags controlling what is included in the device information set built by SetupDiGetClassDevs
            internal const int DIGCF_DEFAULT = 0x00000001;   //only valid with DIGCF_DEVICEINTERFACE
            internal const int DIGCF_PRESENT = 0x00000002;
            internal const int DIGCF_ALLCLASSES = 0x00000004;
            internal const int DIGCF_PROFILE = 0x00000008;
            internal const int DIGCF_DEVICEINTERFACE = 0x00000010;

            internal enum EFileAttributes : uint
                   Readonly         = 0x00000001,
                   Hidden           = 0x00000002,
                   System           = 0x00000004,
                   Directory        = 0x00000010,
                   Archive          = 0x00000020,
                   Device           = 0x00000040,
                   Normal           = 0x00000080,
                   Temporary        = 0x00000100,
                   SparseFile       = 0x00000200,
                   ReparsePoint     = 0x00000400,
                   Compressed       = 0x00000800,
                   Offline          = 0x00001000,
                   NotContentIndexed= 0x00002000,
                   Encrypted        = 0x00004000,
                   Write_Through    = 0x80000000,
                   Overlapped       = 0x40000000,
                   NoBuffering      = 0x20000000,
                   RandomAccess     = 0x10000000,
                   SequentialScan   = 0x08000000,
                   DeleteOnClose    = 0x04000000,
                   BackupSemantics  = 0x02000000,
                   PosixSemantics   = 0x01000000,
                   OpenReparsePoint = 0x00200000,
                   OpenNoRecall     = 0x00100000,
                   FirstPipeInstance= 0x00080000

            internal struct SP_DEVINFO_DATA
                   public uint cbSize;
                   public Guid ClassGuid;
                   public uint DevInst;
                   public IntPtr Reserved;

            internal struct SP_DEVICE_INTERFACE_DATA
                   public int cbSize;
                   public Guid InterfaceClassGuid;
                   public int Flags;
                   public IntPtr RESERVED;

            [StructLayout(LayoutKind.Sequential, Pack = 1)]
            internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
                   public UInt32 cbSize;
                   [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
                   public string DevicePath;

            public struct HIDD_ATTRIBUTES
                   public int Size;
                   public short VendorID;
                   public short ProductID;
                   public short VersionNumber;

            [DllImport(@"hid.dll", CharSet=CharSet.Auto, SetLastError = true)]
            internal static extern void HidD_GetHidGuid(out Guid gHid);

            internal static extern Boolean HidD_GetAttributes(IntPtr HidDeviceObject, ref HIDD_ATTRIBUTES Attributes);

            internal extern static bool HidD_SetOutputReport(IntPtr HidDeviceObject, byte[] lpReportBuffer, uint ReportBufferLength);

            [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
            internal static extern IntPtr SetupDiGetClassDevs(
                   ref Guid ClassGuid,
                   [MarshalAs(UnmanagedType.LPTStr)] string Enumerator,
                   IntPtr hwndParent,
                   UInt32 Flags);

            [DllImport(@"setupapi.dll", CharSet=CharSet.Auto, SetLastError = true)]
            internal static extern Boolean SetupDiEnumDeviceInterfaces(
                   IntPtr hDevInfo,
                   IntPtr devInvo,
                   ref Guid interfaceClassGuid,
                   UInt32 memberIndex,
                   ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);

            [DllImport(@"setupapi.dll", SetLastError = true)]
            internal static extern Boolean SetupDiGetDeviceInterfaceDetail(
                   IntPtr hDevInfo,
                   ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
                   IntPtr deviceInterfaceDetailData,
                   UInt32 deviceInterfaceDetailDataSize,
                   out UInt32 requiredSize,
                   IntPtr deviceInfoData);

            [DllImport(@"setupapi.dll", SetLastError = true)]
            internal static extern Boolean SetupDiGetDeviceInterfaceDetail(
                   IntPtr hDevInfo,
                   ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
                   ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData,
                   UInt32 deviceInterfaceDetailDataSize,
                   out UInt32 requiredSize,
                   IntPtr deviceInfoData);

            [DllImport(@"setupapi.dll", CharSet=CharSet.Auto, SetLastError = true)]
            internal static extern UInt16 SetupDiDestroyDeviceInfoList(IntPtr hDevInfo);

            [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            internal static extern SafeFileHandle CreateFile(
                   string fileName,
                   [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
                   [MarshalAs(UnmanagedType.U4)] FileShare fileShare,
                   IntPtr securityAttributes,
                   [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
                   [MarshalAs(UnmanagedType.U4)] EFileAttributes flags,
                   IntPtr template);

            [DllImport("kernel32.dll", SetLastError=true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool CloseHandle(IntPtr hObject);

And now on to the fun part. Here is where we enumerate through the connected HIDs. There are a bunch of comments in the code that should guide you through what is going on.

public static string EnumerateHids()
    StringBuilder details = new StringBuilder();
    HIDImports.HIDD_ATTRIBUTES deviceAttributes;
    Guid guid;
    uint index = 0;

    // get the GUID of the HID class
    HIDImports.HidD_GetHidGuid(out guid);

    // get a handle to all devices that are part of the HID class
    IntPtr hDevInfo = HIDImports.SetupDiGetClassDevs(ref guid, null, IntPtr.Zero, HIDImports.DIGCF_DEVICEINTERFACE);

    // create a new interface data struct and initialize its size
    diData.cbSize = Marshal.SizeOf(diData);

    // get a device interface to a single device (enumerate all devices)
    while (HIDImports.SetupDiEnumDeviceInterfaces(hDevInfo, IntPtr.Zero, ref guid, index, ref diData))
        UInt32 size = 0;

        // get the buffer size for this device detail instance (returned in the size parameter)
        HIDImports.SetupDiGetDeviceInterfaceDetail(hDevInfo, ref diData, IntPtr.Zero, 0, out size, IntPtr.Zero);

        // create a detail struct and set its size

        //On Win x86, cbSize = 5, On x64, cbSize = 8
        diDetail.cbSize = (IntPtr.Size == 8) ? (uint)8 : (uint)5;

        // actually get the detail struct
        if (HIDImports.SetupDiGetDeviceInterfaceDetail(hDevInfo, ref diData, ref diDetail, size, out size, IntPtr.Zero))
            // open a read/write handle to our device using the DevicePath returned
            SafeHandle safeHandle = HIDImports.CreateFile(diDetail.DevicePath, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, HIDImports.EFileAttributes.Overlapped, IntPtr.Zero);

            // create an attributes struct and initialize the size
            deviceAttributes = new HIDImports.HIDD_ATTRIBUTES();
            deviceAttributes.Size = Marshal.SizeOf(deviceAttributes);

            // get the attributes of the current device
            if (HIDImports.HidD_GetAttributes(safeHandle.DangerousGetHandle(), ref deviceAttributes))
                details.AppendLine("--- HID DEVICE FOUND ---");
                details.AppendLine(String.Format("ProductID: 0x{0}", deviceAttributes.ProductID.ToString("X4")));
                details.AppendLine(String.Format("VendorID: 0x{0}", deviceAttributes.VendorID.ToString("X4")));
                details.AppendLine(String.Format("VersionNumber: 0x{0}", deviceAttributes.VersionNumber.ToString("X4")));
                details.AppendLine(String.Format("Size: 0x{0}", deviceAttributes.Size.ToString("X4")));
    return details.ToString();

So as you can see, we’re looping over all HIDs and creating a string to let you know which devices are connected and what their attributes are. Now all you have to do is connect to the device that matches your ProductId, VendorId, etc. I may go through doing this in a follow up post, but that’s it for now. Again, I would like to attribute the bulk of this work to the WiiMote codeplex project referenced earlier in the post. Enjoy!!

Read Full Post »