65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 HID 协议与 USB 设备通信

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (37投票s)

2020年7月23日

CPOL

2分钟阅读

viewsIcon

192139

downloadIcon

13995

本文将帮助您了解如何使用 C# 中的 WinAPI 与 USB 设备通信

警告

由于这种 USB 嗅探器以用户模式访问硬件的方式,无法读取 RID 为 0 的 HID 数据包,这是因为 Windows 的保护级别为了防止键盘记录器/间谍软件。

(不要添加 0x,否则应用程序将会崩溃,因为我还没有添加 0x 前缀支持)

背景

这篇文章的实现得益于这个 WDK 示例

基本上,这只是对该示例的重写,但以更简单的方式呈现。

Using the Code

主要代码

void    CheckHIDRead()
{
    HIDReadInfo.Device = new HID_DEVICE[DeviceDiscovery.FindDeviceNumber()];

    DeviceDiscovery.FindKnownHIDDevices(ref HIDReadInfo.Device);

    for (var Index = 0; Index < HIDReadInfo.Device.Length; Index++)
    {
        if (HIDReadInfo.VendorID != 0)
        {
            var Count = 0;

            if (HIDReadInfo.Device[Index].Attributes.VendorID == HIDReadInfo.VendorID)
            {
                Count++;
            }
            if (HIDReadInfo.Device[Index].Attributes.ProductID == HIDReadInfo.ProductID)
            {
                Count++;
            }

            if (Count == 2)
            {
                HIDReadInfo.iDevice = Index;
                HIDReadInfo.Active  = true;

                return;
            }
        }
    }
}
void    CheckHIDWrite()
{
    HIDWriteInfo.Device = new HID_DEVICE[DeviceDiscovery.FindDeviceNumber()];

    DeviceDiscovery.FindKnownHIDDevices(ref HIDWriteInfo.Device);

    for (var Index = 0; Index < HIDWriteInfo.Device.Length; Index++)
    {
        if (HIDWriteInfo.VendorID != 0)
        {
            var Count = 0;

            if (HIDWriteInfo.Device[Index].Attributes.VendorID == HIDWriteInfo.VendorID)
            {
                Count++;
            }
            if (HIDWriteInfo.Device[Index].Attributes.ProductID == HIDWriteInfo.ProductID)
            {
                Count++;
            }
            if (HIDWriteInfo.Device[Index].Caps.UsagePage == HIDWriteInfo.UsagePage)
            {
                Count++;
            }
            if (HIDWriteInfo.Device[Index].Caps.Usage == HIDWriteInfo.Usage)
            {
                Count++;
            }

            if (Count == 4)
            {
                HIDWriteInfo.iDevice = Index;
                HIDWriteInfo.Active  = true;

                return;
            }
        }
    }
}

CheckHIDRead()CheckHIDWrite() 用于检查我们是否按下了 读取发送 按钮,以及输入的 数据 (VID-PID-Usa...) 是否与连接的 USB 设备相对应。

此函数返回要扫描的 USB HID 的数量。

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);
}

此函数返回每个 USB 设备所需的数据结构,用于 HIDRead()HIDWrite()

static public void 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();

    Hid.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));

    for (var iHIDD = 0; iHIDD < HID_Devices.Length; iHIDD++)
    {
        SetupDiEnumDeviceInterfaces(hardwareDeviceInfo, IntPtr.Zero, ref hidGuid, iHIDD, ref deviceInfoData);

        //
        // Allocate a function class device data structure to receive the
        // goods about this particular device.
        //
        var RequiredLength = 0;
        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);
    }
}

此函数扩展了 FindKnownHIDDevices()

static 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 relivant information in the given
    // HID_DEVICE structure.
    //
    HID_Device[iHIDD].DevicePath = DevicePath;

    //
    // The hid.dll api's 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].Handle);
    HID_Device[iHIDD].Handle     = CreateFile(HID_Device[iHIDD].DevicePath,
                                              FileAccess.ReadWrite, FileShare.ReadWrite,
                                              0, FileMode.Open, FileOptions.None,
                                              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.
    //
    Hid.HidD_FreePreparsedData(ref HID_Device[iHIDD].Ppd);
    HID_Device[iHIDD].Ppd = IntPtr.Zero;

    Hid.HidD_GetPreparsedData(HID_Device[iHIDD].Handle, ref HID_Device[iHIDD].Ppd);
    Hid.HidD_GetAttributes   (HID_Device[iHIDD].Handle, ref HID_Device[iHIDD].Attributes);
    Hid.HidP_GetCaps         (HID_Device[iHIDD].Ppd   , ref HID_Device[iHIDD].Caps);

    var Buffer = Marshal.AllocHGlobal(126);
    {
        Hid.HidD_GetManufacturerString(HID_Device[iHIDD].Handle, Buffer, 126);
        HID_Device[iHIDD].Manufacturer = Marshal.PtrToStringAuto(Buffer);

        Hid.HidD_GetProductString(HID_Device[iHIDD].Handle, Buffer, 126);
        HID_Device[iHIDD].Product = Marshal.PtrToStringAuto(Buffer);

        Hid.HidD_GetSerialNumberString(HID_Device[iHIDD].Handle, Buffer, 126);
        HID_Device[iHIDD].SerialNumber = Marshal.PtrToStructure<Int32>(Buffer);
    }
    Marshal.FreeHGlobal(Buffer);
}

接下来是两个重要的函数,它们将使您能够读取或发送 USB HID 和 PC 之间的 HID 数据包。

static async public void BeginAsyncRead(object? state)
{
    //
    // Read what the USB device has sent to the PC and store the result inside HID_Report[]
    //
    if (HIDReadInfo.Active == true)
    {
        var Device       = HIDReadInfo.Device[HIDReadInfo.iDevice];
        var ReportBuffer = new Byte[Device.Caps.InputReportByteLength];

        if (ReportBuffer.Length > 0)
        {
            await Task.Run(() =>
            {
                var NumberOfBytesRead = 0U;
                Kernel32.ReadFile(Device.Handle, ReportBuffer, Device.Caps.InputReportByteLength, ref NumberOfBytesRead, IntPtr.Zero);
            });

            HIDReadInfo.ReportData = new List<Byte>(ReportBuffer);
        }
    }
}

static public void BeginSyncSend(object? state)
{
    //
    // Sent to the USB device what is stored inside WriteData[]
    //
    if (HIDWriteInfo.Done is false && HIDWriteInfo.Active is true)
    {
        var Device = HIDWriteInfo.Device[HIDWriteInfo.iDevice];
        var ReportBuffer = new Byte[Device.Caps.OutputReportByteLength];

        if (ReportBuffer.Length > 0)
        {
            //
            // Add ReportID to the first byte of HID_ReportContent
            //
            ReportBuffer[0] = HIDWriteInfo.ReportID;

            //
            // Copy ReportData into HID_ReportContent starting from index 1
            //
            Array.Copy(HIDWriteInfo.ReportData, 0, ReportBuffer, 1, ReportBuffer.Length - 1);

            var varA = 0U;
            Kernel32.WriteFile(Device.Handle, ReportBuffer, Device.Caps.OutputReportByteLength, ref varA, IntPtr.Zero);
        }

        HIDWriteInfo.Done = true;
    }
}

为了能够做到这一点,您需要先设置以下数据

  • VendorID (供应商ID)
  • ProductID
  • UsagePage (用途页)
  • 用法
  • ReportID (报告ID)

但请小心,您需要为所有这些参数设置正确的值,如果有一个是 false,您将无法发送 HID 数据包。

要读取 HID 数据包,您只需要

  • VendorID (供应商ID)
  • ProductID

此外,如果 USB 设备无法发送数据,您就无法读取,如果您无法读取数据,您也无法写入数据 (在 HID 报告描述符中定义)。

设备由其 VendorID:ProductID 定义,但被缩小到由其 UsagePageUsageReportID 定义的几个函数中。

例如,鼠标的第一个功能是发送坐标数据,因此您可以从 PC 读取数据,第二个功能是接收鼠标按钮自定义数据,因此您可以从 PC 发送数据。

为了设置这些变量,您需要读取目标 USB 设备的 HID 描述符,可以使用 USB 嗅探器(如 https://github.com/djpnewton/busdoghttp://www.usblyzer.com/usb-analysis-features.htm)来检索。

HID 描述符通常以 0x05, 0x01 开头。

要学习读取 HID 描述符,请使用此工具:http://www.usb.org/developers/hidpage#HID Descriptor Toolhttps://docs.linuxkernel.org.cn/hid/hidintro.html

由于此代码只是对 90 年代早期 C 代码的重写,因此它适用于所有 Windows 版本。

历史

  • 2019 年 10 月 2 日:初始版本
  • 2023 年 9 月 10 日:项目从 Windows Forms .NET framework 升级到 WPF .NET Core
© . All rights reserved.