Click here to Skip to main content
15,743,429 members
Articles / Programming Languages / C#
Article
Posted 23 Jul 2020

Stats

159.3K views
12.4K downloads
113 bookmarked

How to Communicate with its USB Devices using HID Protocol

Rate me:
Please Sign up or sign in to vote.
4.80/5 (29 votes)
10 Sep 2023CPOL2 min read
This article will help you to understand how to communicate with the USB devices using WinAPI in C#.
This article shows you how to use the USB/HID protocol under Windows to be able to send/receive USB packets from any USB devices connected to the PC. And without using DLL, just an application is needed.

(Visual Studio 2022 project)

Warning

This USB sniffer, because of its user mode method access to hardware, cannot read HID packets with RID at 0, it's due to Windows protection level to prevent keyloggers/spying software.

Image 1

(Do not add 0x, else the application will crash, I haven't added 0x prefix support)

Introduction

This article shows you how to use the USB/HID protocol under Windows to be able to send/receive USB packets from any USB devices connected to the PC.

And without using DLL, just an application is needed.

Background

This article was possible with this WDK sample:

Basically, it's just a rewrite of this sample, but in a simple form.

Using the Code

Main code:

C#
void    Update()
{
    while (true)
    {
        CheckHIDRead();
        CheckHIDWrite();

        Thread.Sleep(1);
    }
}

CheckHIDRead() and CheckHIDWrite() are checking if we have press Read or Start button and if entered data (VID-PID-Usa***) correspond to a connected USB Device.

This function returns the number of USB devices in order to scan them.

C#
Int32   FindDeviceNumber()
{
    var hidGuid        = new Guid();
    var deviceInfoData = new SP_DEVICE_INTERFACE_DATA();

    HidD_GetHidGuid(ref hidGuid);

    //
    // Open a handle to the plug and play dev node.
    //
    SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
    hardwareDeviceInfo    = SetupDiGetClassDevs(ref hidGuid, IntPtr.Zero, 
                            IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    deviceInfoData.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));

    var Index = 0;
    while (SetupDiEnumDeviceInterfaces(hardwareDeviceInfo, IntPtr.Zero, 
                                       ref hidGuid, Index, ref deviceInfoData))
    {
        Index++;
    }

    return (Index);
}

This function returns a data structure of each USB device needed for Read() and Write().

C#
Int32   FindKnownHIDDevices(ref HID_DEVICE[] HID_Devices)
{
    var hidGuid                 = new Guid();
    var deviceInfoData          = new SP_DEVICE_INTERFACE_DATA();
    var functionClassDeviceData = new SP_DEVICE_INTERFACE_DETAIL_DATA();

    HidD_GetHidGuid(ref hidGuid);

    //
    // Open a handle to the plug and play dev node.
    //
    SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
    hardwareDeviceInfo    = SetupDiGetClassDevs(ref hidGuid, IntPtr.Zero, 
                            IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    deviceInfoData.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));

    var iHIDD = 0;
    while (SetupDiEnumDeviceInterfaces(hardwareDeviceInfo, IntPtr.Zero, 
                                       ref hidGuid, iHIDD, ref deviceInfoData))
    {
        var RequiredLength = 0;

        //
        // Allocate a function class device data structure to receive the
        // goods about this particular device.
        //
        SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo, ref deviceInfoData, 
                                        IntPtr.Zero, 0, ref RequiredLength, IntPtr.Zero);

        if (IntPtr.Size == 8)
        {
            functionClassDeviceData.cbSize = 8;
        }
        else if (IntPtr.Size == 4)
        {
            functionClassDeviceData.cbSize = 5;
        }

        //
        // Retrieve the information from Plug and Play.
        //
        SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo, ref deviceInfoData, 
                                        ref functionClassDeviceData, RequiredLength, 
                                        ref RequiredLength, IntPtr.Zero);

        //
        // Open device with just generic query abilities to begin with
        //
        OpenHidDevice(functionClassDeviceData.DevicePath, ref HID_Devices, iHIDD);

        iHIDD++;
    }

    return iHIDD;
}

This function extend FindKnownHIDDevices().

C#
void    OpenHIDDevice(String DevicePath, ref HID_DEVICE[] HID_Device, Int32 iHIDD)
{
    //
    // RoutineDescription:
    // Given the HardwareDeviceInfo, representing a handle to the plug and
    // play information, and deviceInfoData, representing a specific hid device,
    // open that device and fill in all the relevant information in the given
    // HID_DEVICE structure.
    //
    HID_Device[iHIDD].DevicePath = DevicePath;

    //
    // The hid.dll APIs do not pass the overlapped structure into deviceiocontrol
    // so to use them we must have a non overlapped device. If the request is for
    // an overlapped device, we will close the device below and get a handle to an
    // overlapped device
    //
    CloseHandle(HID_Device[iHIDD].Pointer);
    HID_Device[iHIDD].Pointer    = CreateFile(HID_Device[iHIDD].DevicePath, 
                                   GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | 
                                   FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, IntPtr.Zero);
    HID_Device[iHIDD].Caps       = new HIDP_CAPS();
    HID_Device[iHIDD].Attributes = new HIDD_ATTRIBUTES();

    //
    // If the device was not opened as overlapped, then fill in the rest of the
    // HID_Device structure. However, if opened as overlapped, this handle cannot
    // be used in the calls to the HidD_ exported functions since each of these
    // functions does synchronous I/O.
    //
    HidD_FreePreparsedData(ref HID_Device[iHIDD].Ppd);
    HID_Device[iHIDD].Ppd = IntPtr.Zero;

    HidD_GetPreparsedData(HID_Device[iHIDD].Pointer, ref HID_Device[iHIDD].Ppd);
    HidD_GetAttributes(HID_Device[iHIDD].Pointer, ref HID_Device[iHIDD].Attributes);
    HidP_GetCaps(HID_Device[iHIDD].Ppd, ref HID_Device[iHIDD].Caps);
            
    var Buffer = Marshal.AllocHGlobal(126);
    {
        if (HidD_GetManufacturerString(HID_Device[iHIDD].Pointer, Buffer, 126))
        {
            HID_Device[iHIDD].Manufacturer = Marshal.PtrToStringAuto(Buffer);
        }
        if (HidD_GetProductString(HID_Device[iHIDD].Pointer, Buffer, 126))
        {
            HID_Device[iHIDD].Product = Marshal.PtrToStringAuto(Buffer);
        }
        if (HidD_GetSerialNumberString(HID_Device[iHIDD].Pointer, Buffer, 126))
        {
            Int32.TryParse(Marshal.PtrToStringAuto(Buffer), 
                           out HID_Device[iHIDD].SerialNumber);
        }
    }
    Marshal.FreeHGlobal(Buffer);

    //
    // At this point the client has a choice.  It may choose to look at the
    // Usage and Page of the top level collection found in the HIDP_CAPS
    // structure. In this way --------*it could just use the usages it knows about.
    // If either HidP_GetUsages or HidP_GetUsageValue return an error, then
    // that particular usage does not exist in the report.
    // This is most likely the preferred method as the application can only
    // use usages of which it already knows.
    // In this case, the app need not even call GetButtonCaps or GetValueCaps.
    //
    // In this example, however, wSendHID_PIDe will call FillDeviceInfo to look for all
    //    of the usages in the device.
    //
    //FillDeviceInfo(ref HID_Device, iHIDD);
}

Then come the two important functions that will make you able to read or write USB packets between a USB device and a PC.

C#
void    HIDRead(HID_DEVICE HID_Device)
{
    ManufacturerName.Text = "Manufacturer: " + HID_Device.Manufacturer;
    ProductName.Text      = "Product: "      + HID_Device.Product;
    SerialNumber.Text     = "SerialNumber: " + HID_Device.SerialNumber.ToString();

    //
    // Read what the USB device has sent to the PC and store the result into HID_Report[]
    //
    var HID_Report = new Byte[HID_Device.Caps.InputReportByteLength];
            
    if (HID_Report.Length > 0)
    {
        var varA = 0U;
        ReadFile(HID_Device.Pointer, HID_Report, 
                 HID_Device.Caps.InputReportByteLength, ref varA, IntPtr.Zero);

        Read_Output.Clear();

        for (var Index = 0; Index < HID_Device.Caps.InputReportByteLength; Index++)
        {
            Read_Output.Text += HID_Report[Index].ToString("X2");
            Read_Output.Text += " - ";
        }
    }
}
void    HIDWrite(HID_DEVICE HID_Device)
{
    //
    // Sent to the USB device what is stored in WriteData[]
    //
    var HID_Report = new Byte[HID_Device.Caps.OutputReportByteLength];

    if (HID_Report.Length > 0)
    {
        HID_Report[0] = HIDWriteData.ReportID;

        for (var Index = 0; Index < WriteData.Length; Index++)
        {
            if (Index + 1 < HID_Report.Length)
            {
                // Start at 1, as the first byte must be zero for HID report
                HID_Report[Index + 1] = WriteData[Index];
            }
        }

        var varA = 0U;
        WriteFile(HID_Device.Pointer, HID_Report, 
                  HID_Device.Caps.OutputReportByteLength, ref varA, IntPtr.Zero);
    }
}

To be able to do that, you'd need to set the following data before:

  • VendorID
  • ProductID
  • UsagePage
  • Usage
  • ReportID

But be careful, you'd need to set the correct values for all those parameters, if one is false, you will not be able to send HID packets.

To read HID packets, you just need:

  • VendorID
  • ProductID

Also, you cannot read if the device cannot send data and you cannot write if the device cannot read data (defined by the HID Report Descriptor).

A device is defined by its VendorID:ProductID but shrunk into several functions defined by its UsagePage, Usage and ReportID.

As an example, the first function of a mouse is to send coordinate data, so you can read data from PC and the second function is to receive mouse button customization data, so you can send data from PC.

And to set those variables, you need to read the HID Descriptor of the USB devices that you target, it can be retrieved with a USB sniffer as https://github.com/djpnewton/busdog or http://www.usblyzer.com/usb-analysis-features.htm

The HID Descriptor usually start with 0x05, 0x01.

And to learn to read HID Descriptor, use this tool: http://www.usb.org/developers/hidpage#HID Descriptor Tool

Because this code is just a rewrite of an old C code from the 90s, it works on all Windows Versions.

History

  • 2nd October, 2019: Initial version
  • 10th September, 2023: Project upgrade from Windows Forms .NET framework to WPF .NET Core

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
BugTrash in source code ZIP Pin
Sergey Alexandrovich Kryukov10-Sep-23 5:51
mvaSergey Alexandrovich Kryukov10-Sep-23 5:51 
QuestionException in read to run from VS IDE, but it's ok to run from command line Pin
Felix_Shih18-Jul-23 21:52
Felix_Shih18-Jul-23 21:52 
AnswerRe: Exception in read to run from VS IDE, but it's ok to run from command line Pin
wqaxs3610-Sep-23 2:32
wqaxs3610-Sep-23 2:32 
QuestionReading incoming data Pin
Member 767223613-Feb-23 5:01
Member 767223613-Feb-23 5:01 
AnswerRe: Reading incoming data Pin
wqaxs3613-Feb-23 7:37
wqaxs3613-Feb-23 7:37 
GeneralRe: Reading incoming data Pin
Member 1591936213-Feb-23 12:13
Member 1591936213-Feb-23 12:13 
GeneralRe: Reading incoming data Pin
wqaxs3613-Feb-23 21:11
wqaxs3613-Feb-23 21:11 
GeneralRe: Reading incoming data Pin
Member 767223614-Feb-23 4:40
Member 767223614-Feb-23 4:40 
QuestionSend data to microcontroller Pin
Eduard Bumbu11-Mar-22 22:09
Eduard Bumbu11-Mar-22 22:09 
AnswerRe: Send data to microcontroller Pin
wqaxs3612-Mar-22 5:55
wqaxs3612-Mar-22 5:55 
GeneralRe: Send data to microcontroller Pin
don_ucw13-May-22 3:56
don_ucw13-May-22 3:56 
GeneralRe: Send data to microcontroller Pin
wqaxs3613-May-22 6:45
wqaxs3613-May-22 6:45 
Questionwhat is Report ID (RID) Pin
AliAhmadSabir6-Jun-21 3:54
AliAhmadSabir6-Jun-21 3:54 
AnswerRe: what is Report ID (RID) Pin
wqaxs366-Jun-21 5:35
wqaxs366-Jun-21 5:35 
QuestionIt does not work with HID barcode reader Pin
Member 146350394-Oct-20 18:33
Member 146350394-Oct-20 18:33 
AnswerRe: It does not work with HID barcode reader Pin
wqaxs365-Oct-20 5:25
wqaxs365-Oct-20 5:25 
QuestionGreat tutorial about USB HID Pin
Potter6830-Aug-20 7:24
Potter6830-Aug-20 7:24 
QuestionControlling serial and keyboard emulated devices Pin
uzayim27-Jul-20 12:24
uzayim27-Jul-20 12:24 
AnswerRe: Controlling serial and keyboard emulated devices Pin
wqaxs3627-Jul-20 22:48
wqaxs3627-Jul-20 22:48 
PraiseThank you for this +5 Pin
honey the codewitch23-Jul-20 21:12
mvahoney the codewitch23-Jul-20 21:12 
QuestionI think that I'm running into "cannot read HID packets with RID at 0"... can you please elaborate...? Pin
crn1147-Jun-20 7:34
crn1147-Jun-20 7:34 
AnswerRe: I think that I'm running into "cannot read HID packets with RID at 0"... can you please elaborate...? Pin
wqaxs3614-Jun-20 22:34
wqaxs3614-Jun-20 22:34 
AnswerRe: I think that I'm running into "cannot read HID packets with RID at 0"... can you please elaborate...? Pin
wqaxs3614-Jun-20 22:52
wqaxs3614-Jun-20 22:52 
QuestionI have a VID and a PID, I enter it and hit submit to read... only see zeros in window... what am I doing wrong? Pin
crn1146-Jun-20 3:00
crn1146-Jun-20 3:00 
AnswerRe: I have a VID and a PID, I enter it and hit submit to read... only see zeros in window... what am I doing wrong? Pin
wqaxs3614-Jun-20 22:29
wqaxs3614-Jun-20 22:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.