基于C#的USB HID设备读取测试软件

USB HID设备测试工具,支持设备枚举、数据读写、实时监控、报文分析等功能。


一、项目结构

复制代码
UsbHidTestTool/
├── UsbHidTestTool.csproj
├── Program.cs
├── MainForm.cs
├── MainForm.Designer.cs
├── HidDevice.cs
├── HidReport.cs
├── HidCapabilities.cs
├── UsbDeviceInfo.cs
├── HidEnumerator.cs
├── HexViewer.cs
├── PacketAnalyzer.cs
├── Properties/
└── Resources/

二、核心HID设备类

HidDevice.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace UsbHidTestTool
{
    /// <summary>
    /// HID设备类,封装Windows HID API
    /// </summary>
    public class HidDevice : IDisposable
    {
        // Windows API常量
        private const int FILE_SHARE_READ = 0x00000001;
        private const int FILE_SHARE_WRITE = 0x00000002;
        private const int OPEN_EXISTING = 3;
        private const int GENERIC_READ = 0x80000000;
        private const int GENERIC_WRITE = 0x40000000;
        private const int INVALID_HANDLE_VALUE = -1;
        
        // HID使用常量
        private const int HIDP_STATUS_SUCCESS = 0x110000;
        private const int HIDP_STATUS_NULL = 0x00110000;
        
        // Windows API结构体
        [StructLayout(LayoutKind.Sequential)]
        private struct HIDD_ATTRIBUTES
        {
            public int Size;
            public ushort VendorID;
            public ushort ProductID;
            public ushort VersionNumber;
        }
        
        [StructLayout(LayoutKind.Sequential)]
        private struct HIDP_CAPS
        {
            public ushort Usage;
            public ushort UsagePage;
            public ushort InputReportByteLength;
            public ushort OutputReportByteLength;
            public ushort FeatureReportByteLength;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
            public ushort[] Reserved;
            public ushort NumberLinkCollectionNodes;
            public ushort NumberInputButtonCaps;
            public ushort NumberInputValueCaps;
            public ushort NumberInputDataIndices;
            public ushort NumberOutputButtonCaps;
            public ushort NumberOutputValueCaps;
            public ushort NumberOutputDataIndices;
            public ushort NumberFeatureButtonCaps;
            public ushort NumberFeatureValueCaps;
            public ushort NumberFeatureDataIndices;
        }
        
        [StructLayout(LayoutKind.Sequential)]
        private struct SP_DEVICE_INTERFACE_DATA
        {
            public int cbSize;
            public Guid InterfaceClassGuid;
            public int Flags;
            public IntPtr Reserved;
        }
        
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct SP_DEVICE_INTERFACE_DETAIL_DATA
        {
            public int cbSize;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string DevicePath;
        }
        
        // Windows API函数
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetAttributes(IntPtr hDevice, ref HIDD_ATTRIBUTES Attributes);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetPreparsedData(IntPtr hDevice, out IntPtr PreparsedData);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_FreePreparsedData(IntPtr PreparsedData);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern int HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_SetFeature(IntPtr hDevice, byte[] lpReportBuffer, int ReportBufferLength);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetFeature(IntPtr hDevice, byte[] lpReportBuffer, int ReportBufferLength);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetManufacturerString(IntPtr hDevice, IntPtr Buffer, int BufferLength);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetProductString(IntPtr hDevice, IntPtr Buffer, int BufferLength);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetSerialNumberString(IntPtr hDevice, IntPtr Buffer, int BufferLength);
        
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern IntPtr CreateFile(
            string lpFileName,
            uint dwDesiredAccess,
            uint dwShareMode,
            IntPtr lpSecurityAttributes,
            uint dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile);
        
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);
        
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool ReadFile(
            IntPtr hFile,
            byte[] lpBuffer,
            uint nNumberOfBytesToRead,
            out uint lpNumberOfBytesRead,
            IntPtr lpOverlapped);
        
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool WriteFile(
            IntPtr hFile,
            byte[] lpBuffer,
            uint nNumberOfBytesToWrite,
            out uint lpNumberOfBytesWritten,
            IntPtr lpOverlapped);
        
        [DllImport("setupapi.dll", SetLastError = true)]
        private static extern IntPtr SetupDiGetClassDevs(
            ref Guid ClassGuid,
            IntPtr Enumerator,
            IntPtr hwndParent,
            uint Flags);
        
        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern bool SetupDiEnumDeviceInterfaces(
            IntPtr hDevInfo,
            IntPtr devInfo,
            ref Guid interfaceClassGuid,
            uint memberIndex,
            ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
        
        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern bool SetupDiGetDeviceInterfaceDetail(
            IntPtr hDevInfo,
            ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
            IntPtr deviceInterfaceDetailData,
            uint deviceInterfaceDetailDataSize,
            out uint requiredSize,
            IntPtr deviceInfoData);
        
        [DllImport("setupapi.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetupDiDestroyDeviceInfoList(IntPtr hDevInfo);
        
        // HID设备GUID
        private static readonly Guid GUID_DEVINTERFACE_HID = 
            new Guid("4D1E55B2-F16F-11CF-88CB-001111000030");
        
        // 设备句柄
        private IntPtr _deviceHandle = IntPtr.Zero;
        
        // 设备信息
        public string DevicePath { get; private set; }
        public ushort VendorID { get; private set; }
        public ushort ProductID { get; private set; }
        public ushort Version { get; private set; }
        public string Manufacturer { get; private set; } = string.Empty;
        public string Product { get; private set; } = string.Empty;
        public string SerialNumber { get; private set; } = string.Empty;
        public HidCapabilities Capabilities { get; private set; }
        
        // 状态
        public bool IsOpen => _deviceHandle != IntPtr.Zero && _deviceHandle != (IntPtr)INVALID_HANDLE_VALUE;
        
        /// <summary>
        /// 打开HID设备
        /// </summary>
        public bool Open(string devicePath)
        {
            DevicePath = devicePath;
            
            // 打开设备
            _deviceHandle = CreateFile(
                devicePath,
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                IntPtr.Zero,
                OPEN_EXISTING,
                0,
                IntPtr.Zero);
            
            if (_deviceHandle == (IntPtr)INVALID_HANDLE_VALUE)
            {
                // 如果读写打开失败,尝试只读打开
                _deviceHandle = CreateFile(
                    devicePath,
                    GENERIC_READ,
                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                    IntPtr.Zero,
                    OPEN_EXISTING,
                    0,
                    IntPtr.Zero);
            }
            
            if (_deviceHandle == (IntPtr)INVALID_HANDLE_VALUE)
            {
                return false;
            }
            
            // 获取设备属性
            HIDD_ATTRIBUTES attributes = new HIDD_ATTRIBUTES();
            attributes.Size = Marshal.SizeOf(attributes);
            
            if (!HidD_GetAttributes(_deviceHandle, ref attributes))
            {
                Close();
                return false;
            }
            
            VendorID = attributes.VendorID;
            ProductID = attributes.ProductID;
            Version = attributes.VersionNumber;
            
            // 获取设备描述
            GetDeviceStrings();
            
            // 获取设备能力
            GetDeviceCapabilities();
            
            return true;
        }
        
        /// <summary>
        /// 关闭设备
        /// </summary>
        public void Close()
        {
            if (_deviceHandle != IntPtr.Zero && _deviceHandle != (IntPtr)INVALID_HANDLE_VALUE)
            {
                CloseHandle(_deviceHandle);
                _deviceHandle = IntPtr.Zero;
            }
        }
        
        /// <summary>
        /// 获取设备字符串信息
        /// </summary>
        private void GetDeviceStrings()
        {
            try
            {
                const int bufferSize = 256;
                
                // 获取制造商字符串
                IntPtr buffer = Marshal.AllocHGlobal(bufferSize * 2);
                if (HidD_GetManufacturerString(_deviceHandle, buffer, bufferSize))
                {
                    Manufacturer = Marshal.PtrToStringUni(buffer) ?? string.Empty;
                }
                
                // 获取产品字符串
                if (HidD_GetProductString(_deviceHandle, buffer, bufferSize))
                {
                    Product = Marshal.PtrToStringUni(buffer) ?? string.Empty;
                }
                
                // 获取序列号字符串
                if (HidD_GetSerialNumberString(_deviceHandle, buffer, bufferSize))
                {
                    SerialNumber = Marshal.PtrToStringUni(buffer) ?? string.Empty;
                }
                
                Marshal.FreeHGlobal(buffer);
            }
            catch
            {
                // 忽略错误
            }
        }
        
        /// <summary>
        /// 获取设备能力
        /// </summary>
        private void GetDeviceCapabilities()
        {
            if (!HidD_GetPreparsedData(_deviceHandle, out IntPtr preparsedData))
                return;
            
            try
            {
                HIDP_CAPS caps = new HIDP_CAPS();
                int result = HidP_GetCaps(preparsedData, out caps);
                
                if (result == HIDP_STATUS_SUCCESS)
                {
                    Capabilities = new HidCapabilities
                    {
                        InputReportByteLength = caps.InputReportByteLength,
                        OutputReportByteLength = caps.OutputReportByteLength,
                        FeatureReportByteLength = caps.FeatureReportByteLength,
                        Usage = caps.Usage,
                        UsagePage = caps.UsagePage
                    };
                }
            }
            finally
            {
                if (preparsedData != IntPtr.Zero)
                {
                    HidD_FreePreparsedData(preparsedData);
                }
            }
        }
        
        /// <summary>
        /// 读取输入报告
        /// </summary>
        public bool ReadInputReport(out byte[] buffer)
        {
            buffer = null;
            
            if (!IsOpen || Capabilities.InputReportByteLength == 0)
                return false;
            
            int reportLength = Capabilities.InputReportByteLength;
            buffer = new byte[reportLength];
            
            uint bytesRead = 0;
            bool success = ReadFile(_deviceHandle, buffer, (uint)reportLength, out bytesRead, IntPtr.Zero);
            
            if (success && bytesRead > 0)
            {
                if (bytesRead < buffer.Length)
                {
                    Array.Resize(ref buffer, (int)bytesRead);
                }
                return true;
            }
            
            return false;
        }
        
        /// <summary>
        /// 异步读取输入报告
        /// </summary>
        public bool ReadInputReportAsync(out byte[] buffer, int timeout = 1000)
        {
            buffer = null;
            
            if (!IsOpen || Capabilities.InputReportByteLength == 0)
                return false;
            
            int reportLength = Capabilities.InputReportByteLength;
            buffer = new byte[reportLength];
            
            // 使用异步读取
            var asyncResult = BeginRead(buffer, 0, reportLength, null, null);
            
            if (asyncResult.AsyncWaitHandle.WaitOne(timeout))
            {
                uint bytesRead = 0;
                bool success = EndRead(asyncResult, out bytesRead);
                
                if (success && bytesRead > 0)
                {
                    if (bytesRead < buffer.Length)
                    {
                        Array.Resize(ref buffer, (int)bytesRead);
                    }
                    return true;
                }
            }
            
            return false;
        }
        
        private IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            return new HidAsyncResult(state);
        }
        
        private bool EndRead(IAsyncResult asyncResult, out uint bytesRead)
        {
            bytesRead = 0;
            
            if (!IsOpen)
                return false;
            
            return ReadFile(_deviceHandle, new byte[Capabilities.InputReportByteLength], 
                (uint)Capabilities.InputReportByteLength, out bytesRead, IntPtr.Zero);
        }
        
        /// <summary>
        /// 写入输出报告
        /// </summary>
        public bool WriteOutputReport(byte[] data)
        {
            if (!IsOpen || data == null)
                return false;
            
            // 如果需要,在数据前添加报告ID
            byte[] reportData = data;
            if (Capabilities.OutputReportByteLength > 0 && 
                data.Length < Capabilities.OutputReportByteLength)
            {
                reportData = new byte[Capabilities.OutputReportByteLength];
                Array.Copy(data, 0, reportData, 0, data.Length);
            }
            
            uint bytesWritten = 0;
            bool success = WriteFile(_deviceHandle, reportData, (uint)reportData.Length, 
                out bytesWritten, IntPtr.Zero);
            
            return success && bytesWritten > 0;
        }
        
        /// <summary>
        /// 读取特征报告
        /// </summary>
        public bool ReadFeatureReport(byte reportId, out byte[] buffer)
        {
            buffer = null;
            
            if (!IsOpen || Capabilities.FeatureReportByteLength == 0)
                return false;
            
            int reportLength = Capabilities.FeatureReportByteLength;
            buffer = new byte[reportLength];
            buffer[0] = reportId;  // 报告ID放在第一个字节
            
            bool success = HidD_GetFeature(_deviceHandle, buffer, reportLength);
            
            if (success)
            {
                return true;
            }
            
            return false;
        }
        
        /// <summary>
        /// 写入特征报告
        /// </summary>
        public bool WriteFeatureReport(byte[] data)
        {
            if (!IsOpen || data == null || data.Length == 0)
                return false;
            
            int reportLength = Math.Max(data.Length, Capabilities.FeatureReportByteLength);
            byte[] reportData = new byte[reportLength];
            Array.Copy(data, 0, reportData, 0, Math.Min(data.Length, reportLength));
            
            bool success = HidD_SetFeature(_deviceHandle, reportData, reportLength);
            return success;
        }
        
        public void Dispose()
        {
            Close();
        }
        
        // 异步结果包装类
        private class HidAsyncResult : IAsyncResult
        {
            public object AsyncState { get; }
            public System.Threading.WaitHandle AsyncWaitHandle { get; }
            public bool CompletedSynchronously => false;
            public bool IsCompleted => false;
            
            public HidAsyncResult(object state)
            {
                AsyncState = state;
                AsyncWaitHandle = new System.Threading.ManualResetEvent(false);
            }
        }
    }
}

三、数据结构和枚举器

HidCapabilities.cs

csharp 复制代码
namespace UsbHidTestTool
{
    public class HidCapabilities
    {
        public ushort Usage { get; set; }
        public ushort UsagePage { get; set; }
        public ushort InputReportByteLength { get; set; }
        public ushort OutputReportByteLength { get; set; }
        public ushort FeatureReportByteLength { get; set; }
        
        public override string ToString()
        {
            return $"Usage: 0x{Usage:X4}, UsagePage: 0x{UsagePage:X4}, " +
                   $"Input: {InputReportByteLength} bytes, " +
                   $"Output: {OutputReportByteLength} bytes, " +
                   $"Feature: {FeatureReportByteLength} bytes";
        }
    }
}

UsbDeviceInfo.cs

csharp 复制代码
namespace UsbHidTestTool
{
    public class UsbDeviceInfo
    {
        public string DevicePath { get; set; }
        public ushort VendorID { get; set; }
        public ushort ProductID { get; set; }
        public ushort Version { get; set; }
        public string Manufacturer { get; set; }
        public string Product { get; set; }
        public string SerialNumber { get; set; }
        public HidCapabilities Capabilities { get; set; }
        
        public string Description
        {
            get
            {
                if (!string.IsNullOrEmpty(Product))
                    return $"{Manufacturer} {Product} (VID:{VendorID:X4} PID:{ProductID:X4})";
                else
                    return $"HID Device (VID:{VendorID:X4} PID:{ProductID:X4})";
            }
        }
    }
}

HidEnumerator.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace UsbHidTestTool
{
    public static class HidEnumerator
    {
        [DllImport("setupapi.dll", SetLastError = true)]
        private static extern IntPtr SetupDiGetClassDevs(
            ref Guid ClassGuid,
            IntPtr Enumerator,
            IntPtr hwndParent,
            uint Flags);
        
        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern bool SetupDiEnumDeviceInterfaces(
            IntPtr hDevInfo,
            IntPtr devInfo,
            ref Guid interfaceClassGuid,
            uint memberIndex,
            ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
        
        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern bool SetupDiGetDeviceInterfaceDetail(
            IntPtr hDevInfo,
            ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
            IntPtr deviceInterfaceDetailData,
            uint deviceInterfaceDetailDataSize,
            out uint requiredSize,
            IntPtr deviceInfoData);
        
        [DllImport("setupapi.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetupDiDestroyDeviceInfoList(IntPtr hDevInfo);
        
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern IntPtr CreateFile(
            string lpFileName,
            uint dwDesiredAccess,
            uint dwShareMode,
            IntPtr lpSecurityAttributes,
            uint dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile);
        
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetAttributes(IntPtr hDevice, ref HIDD_ATTRIBUTES Attributes);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetManufacturerString(IntPtr hDevice, IntPtr Buffer, int BufferLength);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetProductString(IntPtr hDevice, IntPtr Buffer, int BufferLength);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetSerialNumberString(IntPtr hDevice, IntPtr Buffer, int BufferLength);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_GetPreparsedData(IntPtr hDevice, out IntPtr PreparsedData);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern bool HidD_FreePreparsedData(IntPtr PreparsedData);
        
        [DllImport("hid.dll", SetLastError = true)]
        private static extern int HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities);
        
        private const uint DIGCF_PRESENT = 0x00000002;
        private const uint DIGCF_DEVICEINTERFACE = 0x00000010;
        private const int INVALID_HANDLE_VALUE = -1;
        private const uint GENERIC_READ = 0x80000000;
        private const uint GENERIC_WRITE = 0x40000000;
        private const int FILE_SHARE_READ = 0x00000001;
        private const int FILE_SHARE_WRITE = 0x00000002;
        private const int OPEN_EXISTING = 3;
        
        private static readonly Guid GUID_DEVINTERFACE_HID = 
            new Guid("4D1E55B2-F16F-11CF-88CB-001111000030");
        
        [StructLayout(LayoutKind.Sequential)]
        private struct SP_DEVICE_INTERFACE_DATA
        {
            public int cbSize;
            public Guid InterfaceClassGuid;
            public int Flags;
            public IntPtr Reserved;
        }
        
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct SP_DEVICE_INTERFACE_DETAIL_DATA
        {
            public int cbSize;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string DevicePath;
        }
        
        [StructLayout(LayoutKind.Sequential)]
        private struct HIDD_ATTRIBUTES
        {
            public int Size;
            public ushort VendorID;
            public ushort ProductID;
            public ushort VersionNumber;
        }
        
        [StructLayout(LayoutKind.Sequential)]
        private struct HIDP_CAPS
        {
            public ushort Usage;
            public ushort UsagePage;
            public ushort InputReportByteLength;
            public ushort OutputReportByteLength;
            public ushort FeatureReportByteLength;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
            public ushort[] Reserved;
            public ushort NumberLinkCollectionNodes;
            public ushort NumberInputButtonCaps;
            public ushort NumberInputValueCaps;
            public ushort NumberInputDataIndices;
            public ushort NumberOutputButtonCaps;
            public ushort NumberOutputValueCaps;
            public ushort NumberOutputDataIndices;
            public ushort NumberFeatureButtonCaps;
            public ushort NumberFeatureValueCaps;
            public ushort NumberFeatureDataIndices;
        }
        
        /// <summary>
        /// 枚举所有HID设备
        /// </summary>
        public static List<UsbDeviceInfo> EnumerateDevices()
        {
            List<UsbDeviceInfo> devices = new List<UsbDeviceInfo>();
            
            // 获取设备信息集
            IntPtr deviceInfoSet = SetupDiGetClassDevs(
                ref GUID_DEVINTERFACE_HID,
                IntPtr.Zero,
                IntPtr.Zero,
                DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
            
            if (deviceInfoSet == IntPtr.Zero)
                return devices;
            
            try
            {
                uint deviceIndex = 0;
                SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
                deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData);
                
                // 枚举设备
                while (SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, 
                    ref GUID_DEVINTERFACE_HID, deviceIndex, ref deviceInterfaceData))
                {
                    // 获取设备路径
                    uint requiredSize = 0;
                    SetupDiGetDeviceInterfaceDetail(
                        deviceInfoSet,
                        ref deviceInterfaceData,
                        IntPtr.Zero,
                        0,
                        out requiredSize,
                        IntPtr.Zero);
                    
                    if (requiredSize > 0)
                    {
                        IntPtr detailDataBuffer = Marshal.AllocHGlobal((int)requiredSize);
                        Marshal.WriteInt32(detailDataBuffer, 
                            IntPtr.Size == 8 ? 8 : 4 + Marshal.SystemDefaultCharSize);
                        
                        if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, 
                            ref deviceInterfaceData, detailDataBuffer, requiredSize, 
                            out requiredSize, IntPtr.Zero))
                        {
                            string devicePath = Marshal.PtrToStringAuto(
                                new IntPtr(detailDataBuffer.ToInt64() + 4));
                            
                            // 获取设备信息
                            UsbDeviceInfo deviceInfo = GetDeviceInfo(devicePath);
                            if (deviceInfo != null)
                            {
                                devices.Add(deviceInfo);
                            }
                        }
                        
                        Marshal.FreeHGlobal(detailDataBuffer);
                    }
                    
                    deviceIndex++;
                }
            }
            finally
            {
                SetupDiDestroyDeviceInfoList(deviceInfoSet);
            }
            
            return devices;
        }
        
        /// <summary>
        /// 获取单个设备信息
        /// </summary>
        private static UsbDeviceInfo GetDeviceInfo(string devicePath)
        {
            // 打开设备
            IntPtr deviceHandle = CreateFile(
                devicePath,
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                IntPtr.Zero,
                OPEN_EXISTING,
                0,
                IntPtr.Zero);
            
            if (deviceHandle == (IntPtr)INVALID_HANDLE_VALUE)
            {
                deviceHandle = CreateFile(
                    devicePath,
                    GENERIC_READ,
                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                    IntPtr.Zero,
                    OPEN_EXISTING,
                    0,
                    IntPtr.Zero);
            }
            
            if (deviceHandle == (IntPtr)INVALID_HANDLE_VALUE)
                return null;
            
            try
            {
                // 获取设备属性
                HIDD_ATTRIBUTES attributes = new HIDD_ATTRIBUTES();
                attributes.Size = Marshal.SizeOf(attributes);
                
                if (!HidD_GetAttributes(deviceHandle, ref attributes))
                    return null;
                
                // 创建设备信息对象
                UsbDeviceInfo deviceInfo = new UsbDeviceInfo
                {
                    DevicePath = devicePath,
                    VendorID = attributes.VendorID,
                    ProductID = attributes.ProductID,
                    Version = attributes.VersionNumber
                };
                
                // 获取设备字符串
                const int bufferSize = 256;
                IntPtr buffer = Marshal.AllocHGlobal(bufferSize * 2);
                
                if (HidD_GetManufacturerString(deviceHandle, buffer, bufferSize))
                {
                    deviceInfo.Manufacturer = Marshal.PtrToStringUni(buffer) ?? string.Empty;
                }
                
                if (HidD_GetProductString(deviceHandle, buffer, bufferSize))
                {
                    deviceInfo.Product = Marshal.PtrToStringUni(buffer) ?? string.Empty;
                }
                
                if (HidD_GetSerialNumberString(deviceHandle, buffer, bufferSize))
                {
                    deviceInfo.SerialNumber = Marshal.PtrToStringUni(buffer) ?? string.Empty;
                }
                
                Marshal.FreeHGlobal(buffer);
                
                // 获取设备能力
                if (HidD_GetPreparsedData(deviceHandle, out IntPtr preparsedData))
                {
                    try
                    {
                        HIDP_CAPS caps = new HIDP_CAPS();
                        int result = HidP_GetCaps(preparsedData, out caps);
                        
                        if (result == 0x110000) // HIDP_STATUS_SUCCESS
                        {
                            deviceInfo.Capabilities = new HidCapabilities
                            {
                                InputReportByteLength = caps.InputReportByteLength,
                                OutputReportByteLength = caps.OutputReportByteLength,
                                FeatureReportByteLength = caps.FeatureReportByteLength,
                                Usage = caps.Usage,
                                UsagePage = caps.UsagePage
                            };
                        }
                    }
                    finally
                    {
                        HidD_FreePreparsedData(preparsedData);
                    }
                }
                
                return deviceInfo;
            }
            finally
            {
                CloseHandle(deviceHandle);
            }
        }
    }
}

四、十六进制查看器控件

HexViewer.cs

csharp 复制代码
using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace UsbHidTestTool
{
    public class HexViewer : RichTextBox
    {
        private const int BYTES_PER_LINE = 16;
        
        public HexViewer()
        {
            this.Font = new Font("Consolas", 10);
            this.BackColor = Color.Black;
            this.ForeColor = Color.LightGray;
            this.ReadOnly = true;
            this.WordWrap = false;
            this.ScrollBars = RichTextBoxScrollBars.Both;
        }
        
        /// <summary>
        /// 显示十六进制数据
        /// </summary>
        public void DisplayData(byte[] data, string title = null)
        {
            this.Clear();
            
            if (data == null || data.Length == 0)
            {
                this.AppendText("No data");
                return;
            }
            
            StringBuilder sb = new StringBuilder();
            
            if (!string.IsNullOrEmpty(title))
            {
                sb.AppendLine(title);
                sb.AppendLine(new string('-', 80));
            }
            
            for (int i = 0; i < data.Length; i += BYTES_PER_LINE)
            {
                // 地址
                sb.AppendFormat("{0:X8}:  ", i);
                
                // 十六进制
                for (int j = 0; j < BYTES_PER_LINE; j++)
                {
                    if (i + j < data.Length)
                        sb.AppendFormat("{0:X2} ", data[i + j]);
                    else
                        sb.Append("   ");
                    
                    if (j == 7) sb.Append(" ");
                }
                
                sb.Append(" ");
                
                // ASCII
                for (int j = 0; j < BYTES_PER_LINE; j++)
                {
                    if (i + j < data.Length)
                    {
                        byte b = data[i + j];
                        if (b >= 32 && b <= 126)
                            sb.Append((char)b);
                        else
                            sb.Append(".");
                    }
                    else
                    {
                        sb.Append(" ");
                    }
                }
                
                sb.AppendLine();
            }
            
            this.Text = sb.ToString();
        }
        
        /// <summary>
        /// 添加数据行(实时显示用)
        /// </summary>
        public void AppendDataLine(byte[] data, string prefix = ">>")
        {
            if (data == null || data.Length == 0)
                return;
            
            StringBuilder line = new StringBuilder();
            line.Append($"{prefix} ");
            
            // 时间戳
            line.Append($"[{DateTime.Now:HH:mm:ss.fff}] ");
            
            // 十六进制
            for (int i = 0; i < Math.Min(data.Length, 64); i++)
            {
                line.AppendFormat("{0:X2} ", data[i]);
            }
            
            if (data.Length > 64)
            {
                line.Append("... ");
            }
            
            // ASCII
            line.Append(" ");
            for (int i = 0; i < Math.Min(data.Length, 32); i++)
            {
                byte b = data[i];
                if (b >= 32 && b <= 126)
                    line.Append((char)b);
                else
                    line.Append(".");
            }
            
            if (data.Length > 32)
            {
                line.Append("...");
            }
            
            this.Invoke((MethodInvoker)delegate
            {
                this.AppendText(line.ToString() + Environment.NewLine);
                this.ScrollToCaret();
            });
        }
        
        /// <summary>
        /// 添加消息
        /// </summary>
        public void AppendMessage(string message, Color? color = null)
        {
            this.Invoke((MethodInvoker)delegate
            {
                int start = this.TextLength;
                this.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}{Environment.NewLine}");
                int end = this.TextLength;
                
                if (color.HasValue)
                {
                    this.Select(start, end - start);
                    this.SelectionColor = color.Value;
                    this.Select(end, 0);
                }
                
                this.ScrollToCaret();
            });
        }
        
        /// <summary>
        /// 清除内容
        /// </summary>
        public new void Clear()
        {
            this.Invoke((MethodInvoker)delegate
            {
                base.Clear();
            });
        }
    }
}

五、主界面

MainForm.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.IO;

namespace UsbHidTestTool
{
    public partial class MainForm : Form
    {
        private HidDevice _currentDevice = null;
        private Thread _readThread = null;
        private bool _isReading = false;
        private List<UsbDeviceInfo> _devices = new List<UsbDeviceInfo>();
        
        // 控件
        private ComboBox cmbDevices;
        private Button btnRefresh;
        private Button btnOpen;
        private Button btnClose;
        private Button btnRead;
        private Button btnWrite;
        private Button btnFeatureRead;
        private Button btnFeatureWrite;
        private Button btnStartMonitor;
        private Button btnStopMonitor;
        private Button btnSaveLog;
        private Button btnClearLog;
        
        private HexViewer txtLog;
        private TextBox txtSendData;
        private NumericUpDown numReportId;
        private CheckBox chkAutoRefresh;
        private CheckBox chkShowHex;
        private CheckBox chkShowAscii;
        
        private Label lblStatus;
        private Label lblDeviceInfo;
        private Label lblCapabilities;
        
        private ListBox lstPackets;
        
        public MainForm()
        {
            InitializeComponent();
            RefreshDeviceList();
        }
        
        private void InitializeComponent()
        {
            this.Text = "USB HID设备测试工具";
            this.Size = new Size(1000, 700);
            this.StartPosition = FormStartPosition.CenterScreen;
            
            // 顶部控制面板
            Panel topPanel = new Panel
            {
                Dock = DockStyle.Top,
                Height = 120,
                BorderStyle = BorderStyle.FixedSingle
            };
            
            // 设备选择区域
            GroupBox gbDevice = new GroupBox
            {
                Text = "设备选择",
                Location = new Point(10, 10),
                Size = new Size(400, 100)
            };
            
            cmbDevices = new ComboBox
            {
                Location = new Point(10, 20),
                Size = new Size(300, 21),
                DropDownStyle = ComboBoxStyle.DropDownList
            };
            
            btnRefresh = new Button
            {
                Text = "刷新",
                Location = new Point(320, 20),
                Size = new Size(70, 23)
            };
            btnRefresh.Click += BtnRefresh_Click;
            
            btnOpen = new Button
            {
                Text = "打开",
                Location = new Point(10, 50),
                Size = new Size(70, 23)
            };
            btnOpen.Click += BtnOpen_Click;
            
            btnClose = new Button
            {
                Text = "关闭",
                Location = new Point(90, 50),
                Size = new Size(70, 23),
                Enabled = false
            };
            btnClose.Click += BtnClose_Click;
            
            gbDevice.Controls.AddRange(new Control[] { cmbDevices, btnRefresh, btnOpen, btnClose });
            
            // 设备信息区域
            lblDeviceInfo = new Label
            {
                Location = new Point(420, 20),
                Size = new Size(300, 40),
                Text = "未选择设备"
            };
            
            lblCapabilities = new Label
            {
                Location = new Point(420, 60),
                Size = new Size(300, 40),
                Text = ""
            };
            
            // 状态标签
            lblStatus = new Label
            {
                Location = new Point(730, 20),
                Size = new Size(250, 20),
                Text = "就绪",
                ForeColor = Color.Green
            };
            
            topPanel.Controls.AddRange(new Control[] { gbDevice, lblDeviceInfo, lblCapabilities, lblStatus });
            
            // 中间控制面板
            Panel middlePanel = new Panel
            {
                Dock = DockStyle.Top,
                Height = 120,
                BorderStyle = BorderStyle.FixedSingle
            };
            
            // 发送数据区域
            GroupBox gbSend = new GroupBox
            {
                Text = "发送数据",
                Location = new Point(10, 10),
                Size = new Size(300, 100)
            };
            
            Label lblReportId = new Label
            {
                Text = "报告ID:",
                Location = new Point(10, 20),
                Size = new Size(50, 20)
            };
            
            numReportId = new NumericUpDown
            {
                Location = new Point(60, 20),
                Size = new Size(50, 20),
                Minimum = 0,
                Maximum = 255,
                Value = 0
            };
            
            txtSendData = new TextBox
            {
                Location = new Point(10, 45),
                Size = new Size(280, 20),
                Text = "0102030405060708"
            };
            
            btnWrite = new Button
            {
                Text = "发送输出报告",
                Location = new Point(10, 70),
                Size = new Size(120, 23)
            };
            btnWrite.Click += BtnWrite_Click;
            
            btnFeatureWrite = new Button
            {
                Text = "发送特征报告",
                Location = new Point(140, 70),
                Size = new Size(120, 23)
            };
            btnFeatureWrite.Click += BtnFeatureWrite_Click;
            
            gbSend.Controls.AddRange(new Control[] { 
                lblReportId, numReportId, txtSendData, btnWrite, btnFeatureWrite 
            });
            
            // 接收控制区域
            GroupBox gbReceive = new GroupBox
            {
                Text = "接收控制",
                Location = new Point(320, 10),
                Size = new Size(200, 100)
            };
            
            btnRead = new Button
            {
                Text = "读取输入报告",
                Location = new Point(10, 20),
                Size = new Size(180, 23)
            };
            btnRead.Click += BtnRead_Click;
            
            btnFeatureRead = new Button
            {
                Text = "读取特征报告",
                Location = new Point(10, 50),
                Size = new Size(180, 23)
            };
            btnFeatureRead.Click += BtnFeatureRead_Click;
            
            gbReceive.Controls.AddRange(new Control[] { btnRead, btnFeatureRead });
            
            // 监控控制区域
            GroupBox gbMonitor = new GroupBox
            {
                Text = "实时监控",
                Location = new Point(530, 10),
                Size = new Size(200, 100)
            };
            
            btnStartMonitor = new Button
            {
                Text = "开始监控",
                Location = new Point(10, 20),
                Size = new Size(85, 23)
            };
            btnStartMonitor.Click += BtnStartMonitor_Click;
            
            btnStopMonitor = new Button
            {
                Text = "停止监控",
                Location = new Point(105, 20),
                Size = new Size(85, 23),
                Enabled = false
            };
            btnStopMonitor.Click += BtnStopMonitor_Click;
            
            chkAutoRefresh = new CheckBox
            {
                Text = "自动刷新",
                Location = new Point(10, 50),
                Size = new Size(100, 20),
                Checked = true
            };
            
            chkShowHex = new CheckBox
            {
                Text = "显示十六进制",
                Location = new Point(10, 70),
                Size = new Size(120, 20),
                Checked = true
            };
            
            chkShowAscii = new CheckBox
            {
                Text = "显示ASCII",
                Location = new Point(120, 70),
                Size = new Size(80, 20),
                Checked = true
            };
            
            gbMonitor.Controls.AddRange(new Control[] { 
                btnStartMonitor, btnStopMonitor, chkAutoRefresh, chkShowHex, chkShowAscii 
            });
            
            middlePanel.Controls.AddRange(new Control[] { gbSend, gbReceive, gbMonitor });
            
            // 底部日志面板
            Panel bottomPanel = new Panel
            {
                Dock = DockStyle.Fill,
                BorderStyle = BorderStyle.FixedSingle
            };
            
            // 选项卡
            TabControl tabControl = new TabControl
            {
                Dock = DockStyle.Fill
            };
            
            // 日志标签页
            TabPage tabLog = new TabPage("通信日志");
            txtLog = new HexViewer
            {
                Dock = DockStyle.Fill
            };
            
            // 日志工具栏
            Panel logToolbar = new Panel
            {
                Dock = DockStyle.Top,
                Height = 30
            };
            
            btnSaveLog = new Button
            {
                Text = "保存日志",
                Location = new Point(10, 3),
                Size = new Size(80, 23)
            };
            btnSaveLog.Click += BtnSaveLog_Click;
            
            btnClearLog = new Button
            {
                Text = "清除日志",
                Location = new Point(100, 3),
                Size = new Size(80, 23)
            };
            btnClearLog.Click += BtnClearLog_Click;
            
            logToolbar.Controls.AddRange(new Control[] { btnSaveLog, btnClearLog });
            
            tabLog.Controls.AddRange(new Control[] { txtLog, logToolbar });
            
            // 数据包列表标签页
            TabPage tabPackets = new TabPage("数据包列表");
            lstPackets = new ListBox
            {
                Dock = DockStyle.Fill,
                Font = new Font("Consolas", 9)
            };
            lstPackets.DoubleClick += LstPackets_DoubleClick;
            
            tabPackets.Controls.Add(lstPackets);
            
            tabControl.TabPages.AddRange(new TabPage[] { tabLog, tabPackets });
            bottomPanel.Controls.Add(tabControl);
            
            // 添加到主窗体
            this.Controls.AddRange(new Control[] { topPanel, middlePanel, bottomPanel });
        }
        
        #region 设备操作
        
        private void RefreshDeviceList()
        {
            try
            {
                txtLog.AppendMessage("正在枚举HID设备...", Color.Blue);
                
                _devices = HidEnumerator.EnumerateDevices();
                cmbDevices.Items.Clear();
                
                foreach (var device in _devices)
                {
                    cmbDevices.Items.Add(device.Description);
                }
                
                if (cmbDevices.Items.Count > 0)
                {
                    cmbDevices.SelectedIndex = 0;
                }
                
                txtLog.AppendMessage($"找到 {_devices.Count} 个HID设备", Color.Green);
            }
            catch (Exception ex)
            {
                txtLog.AppendMessage($"枚举设备失败: {ex.Message}", Color.Red);
            }
        }
        
        private void OpenDevice()
        {
            if (cmbDevices.SelectedIndex < 0 || cmbDevices.SelectedIndex >= _devices.Count)
            {
                MessageBox.Show("请先选择设备", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            try
            {
                var deviceInfo = _devices[cmbDevices.SelectedIndex];
                
                _currentDevice = new HidDevice();
                if (_currentDevice.Open(deviceInfo.DevicePath))
                {
                    UpdateDeviceControls(true);
                    
                    lblDeviceInfo.Text = $"设备: {deviceInfo.Description}";
                    lblCapabilities.Text = $"能力: {deviceInfo.Capabilities}";
                    
                    txtLog.AppendMessage($"已打开设备: {deviceInfo.Description}", Color.Green);
                    txtLog.AppendMessage($"设备路径: {deviceInfo.DevicePath}", Color.DarkGray);
                    txtLog.AppendMessage($"制造商: {deviceInfo.Manufacturer}", Color.DarkGray);
                    txtLog.AppendMessage($"产品: {deviceInfo.Product}", Color.DarkGray);
                    txtLog.AppendMessage($"序列号: {deviceInfo.SerialNumber}", Color.DarkGray);
                    txtLog.AppendMessage($"能力: {deviceInfo.Capabilities}", Color.DarkGray);
                }
                else
                {
                    txtLog.AppendMessage("打开设备失败", Color.Red);
                    _currentDevice.Dispose();
                    _currentDevice = null;
                }
            }
            catch (Exception ex)
            {
                txtLog.AppendMessage($"打开设备失败: {ex.Message}", Color.Red);
                _currentDevice?.Dispose();
                _currentDevice = null;
            }
        }
        
        private void CloseDevice()
        {
            StopMonitor();
            
            _currentDevice?.Close();
            _currentDevice?.Dispose();
            _currentDevice = null;
            
            UpdateDeviceControls(false);
            
            lblDeviceInfo.Text = "未选择设备";
            lblCapabilities.Text = "";
            
            txtLog.AppendMessage("设备已关闭", Color.Green);
        }
        
        private void UpdateDeviceControls(bool isOpen)
        {
            btnOpen.Enabled = !isOpen;
            btnClose.Enabled = isOpen;
            btnRead.Enabled = isOpen;
            btnWrite.Enabled = isOpen;
            btnFeatureRead.Enabled = isOpen;
            btnFeatureWrite.Enabled = isOpen;
            btnStartMonitor.Enabled = isOpen;
        }
        
        #endregion
        
        #region 数据读写
        
        private void ReadInputReport()
        {
            if (_currentDevice == null || !_currentDevice.IsOpen)
            {
                MessageBox.Show("设备未打开", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            
            try
            {
                if (_currentDevice.ReadInputReport(out byte[] data))
                {
                    DisplayReceivedData(data, "输入报告");
                }
                else
                {
                    txtLog.AppendMessage("读取输入报告失败", Color.Red);
                }
            }
            catch (Exception ex)
            {
                txtLog.AppendMessage($"读取输入报告失败: {ex.Message}", Color.Red);
            }
        }
        
        private void WriteOutputReport()
        {
            if (_currentDevice == null || !_currentDevice.IsOpen)
            {
                MessageBox.Show("设备未打开", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            
            try
            {
                byte[] data = ParseHexString(txtSendData.Text);
                if (data == null || data.Length == 0)
                {
                    MessageBox.Show("请输入有效的十六进制数据", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    return;
                }
                
                // 如果需要,在数据前添加报告ID
                byte reportId = (byte)numReportId.Value;
                if (reportId > 0)
                {
                    byte[] newData = new byte[data.Length + 1];
                    newData[0] = reportId;
                    Array.Copy(data, 0, newData, 1, data.Length);
                    data = newData;
                }
                
                txtLog.AppendMessage($"发送数据: {BitConverter.ToString(data)}", Color.Blue);
                
                if (_currentDevice.WriteOutputReport(data))
                {
                    txtLog.AppendMessage("发送成功", Color.Green);
                }
                else
                {
                    txtLog.AppendMessage("发送失败", Color.Red);
                }
            }
            catch (Exception ex)
            {
                txtLog.AppendMessage($"发送失败: {ex.Message}", Color.Red);
            }
        }
        
        private void ReadFeatureReport()
        {
            if (_currentDevice == null || !_currentDevice.IsOpen)
            {
                MessageBox.Show("设备未打开", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            
            try
            {
                byte reportId = (byte)numReportId.Value;
                
                if (_currentDevice.ReadFeatureReport(reportId, out byte[] data))
                {
                    DisplayReceivedData(data, $"特征报告 0x{reportId:X2}");
                }
                else
                {
                    txtLog.AppendMessage($"读取特征报告 0x{reportId:X2} 失败", Color.Red);
                }
            }
            catch (Exception ex)
            {
                txtLog.AppendMessage($"读取特征报告失败: {ex.Message}", Color.Red);
            }
        }
        
        private void WriteFeatureReport()
        {
            if (_currentDevice == null || !_currentDevice.IsOpen)
            {
                MessageBox.Show("设备未打开", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            
            try
            {
                byte[] data = ParseHexString(txtSendData.Text);
                if (data == null || data.Length == 0)
                {
                    MessageBox.Show("请输入有效的十六进制数据", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    return;
                }
                
                byte reportId = (byte)numReportId.Value;
                if (reportId > 0)
                {
                    byte[] newData = new byte[data.Length + 1];
                    newData[0] = reportId;
                    Array.Copy(data, 0, newData, 1, data.Length);
                    data = newData;
                }
                
                txtLog.AppendMessage($"发送特征报告: {BitConverter.ToString(data)}", Color.Blue);
                
                if (_currentDevice.WriteFeatureReport(data))
                {
                    txtLog.AppendMessage("特征报告发送成功", Color.Green);
                }
                else
                {
                    txtLog.AppendMessage("特征报告发送失败", Color.Red);
                }
            }
            catch (Exception ex)
            {
                txtLog.AppendMessage($"发送特征报告失败: {ex.Message}", Color.Red);
            }
        }
        
        private byte[] ParseHexString(string hexString)
        {
            if (string.IsNullOrWhiteSpace(hexString))
                return new byte[0];
            
            hexString = hexString.Replace(" ", "").Replace("-", "").Replace(":", "");
            
            if (hexString.Length % 2 != 0)
            {
                hexString = "0" + hexString;
            }
            
            try
            {
                byte[] data = new byte[hexString.Length / 2];
                for (int i = 0; i < data.Length; i++)
                {
                    data[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
                }
                return data;
            }
            catch
            {
                return null;
            }
        }
        
        private void DisplayReceivedData(byte[] data, string type = "数据")
        {
            if (data == null || data.Length == 0)
                return;
            
            // 显示在日志中
            txtLog.AppendDataLine(data, $"<< {type}");
            
            // 添加到数据包列表
            string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
            string hex = BitConverter.ToString(data).Replace("-", " ");
            string ascii = Encoding.ASCII.GetString(data)
                .Replace("\r", ".")
                .Replace("\n", ".")
                .Replace("\t", ".")
                .Replace("\0", ".");
            
            string display = $"[{timestamp}] {hex}";
            if (chkShowAscii.Checked)
            {
                display += $"  [{ascii}]";
            }
            
            lstPackets.Items.Insert(0, display);
            
            // 限制列表大小
            if (lstPackets.Items.Count > 1000)
            {
                lstPackets.Items.RemoveAt(lstPackets.Items.Count - 1);
            }
        }
        
        #endregion
        
        #region 实时监控
        
        private void StartMonitor()
        {
            if (_currentDevice == null || !_currentDevice.IsOpen)
            {
                MessageBox.Show("设备未打开", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            
            if (_isReading)
                return;
            
            _isReading = true;
            btnStartMonitor.Enabled = false;
            btnStopMonitor.Enabled = true;
            
            _readThread = new Thread(MonitorThread)
            {
                IsBackground = true
            };
            _readThread.Start();
            
            txtLog.AppendMessage("开始实时监控...", Color.Green);
        }
        
        private void StopMonitor()
        {
            _isReading = false;
            
            if (_readThread != null && _readThread.IsAlive)
            {
                _readThread.Join(1000);
            }
            
            btnStartMonitor.Enabled = true;
            btnStopMonitor.Enabled = false;
            
            txtLog.AppendMessage("停止实时监控", Color.Green);
        }
        
        private void MonitorThread()
        {
            while (_isReading && _currentDevice != null && _currentDevice.IsOpen)
            {
                try
                {
                    if (_currentDevice.ReadInputReport(out byte[] data))
                    {
                        Invoke((MethodInvoker)delegate
                        {
                            DisplayReceivedData(data, "监控");
                        });
                    }
                }
                catch (Exception ex)
                {
                    if (_isReading)
                    {
                        Invoke((MethodInvoker)delegate
                        {
                            txtLog.AppendMessage($"监控错误: {ex.Message}", Color.Red);
                        });
                    }
                }
                
                Thread.Sleep(10);
            }
        }
        
        #endregion
        
        #region 工具功能
        
        private void SaveLog()
        {
            using (SaveFileDialog dialog = new SaveFileDialog())
            {
                dialog.Filter = "文本文件|*.txt|所有文件|*.*";
                dialog.FileName = $"HID_Log_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
                
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    try
                    {
                        File.WriteAllText(dialog.FileName, txtLog.Text);
                        txtLog.AppendMessage($"日志已保存到: {dialog.FileName}", Color.Green);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"保存失败: {ex.Message}", "错误", 
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
        }
        
        private void ClearLog()
        {
            txtLog.Clear();
            lstPackets.Items.Clear();
        }
        
        #endregion
        
        #region 事件处理
        
        private void BtnRefresh_Click(object sender, EventArgs e)
        {
            RefreshDeviceList();
        }
        
        private void BtnOpen_Click(object sender, EventArgs e)
        {
            OpenDevice();
        }
        
        private void BtnClose_Click(object sender, EventArgs e)
        {
            CloseDevice();
        }
        
        private void BtnRead_Click(object sender, EventArgs e)
        {
            ReadInputReport();
        }
        
        private void BtnWrite_Click(object sender, EventArgs e)
        {
            WriteOutputReport();
        }
        
        private void BtnFeatureRead_Click(object sender, EventArgs e)
        {
            ReadFeatureReport();
        }
        
        private void BtnFeatureWrite_Click(object sender, EventArgs e)
        {
            WriteFeatureReport();
        }
        
        private void BtnStartMonitor_Click(object sender, EventArgs e)
        {
            StartMonitor();
        }
        
        private void BtnStopMonitor_Click(object sender, EventArgs e)
        {
            StopMonitor();
        }
        
        private void BtnSaveLog_Click(object sender, EventArgs e)
        {
            SaveLog();
        }
        
        private void BtnClearLog_Click(object sender, EventArgs e)
        {
            ClearLog();
        }
        
        private void LstPackets_DoubleClick(object sender, EventArgs e)
        {
            if (lstPackets.SelectedItem != null)
            {
                string selected = lstPackets.SelectedItem.ToString();
                int start = selected.IndexOf("]") + 2;
                if (start < selected.Length)
                {
                    string hexPart = selected.Substring(start).Split('[')[0].Trim();
                    hexPart = hexPart.Replace(" ", "");
                    
                    if (hexPart.Length > 0)
                    {
                        txtLog.DisplayData(ParseHexString(hexPart), "选择的数据包");
                    }
                }
            }
        }
        
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            StopMonitor();
            CloseDevice();
            base.OnFormClosing(e);
        }
        
        #endregion
    }
}

六、程序入口

Program.cs

csharp 复制代码
using System;
using System.Windows.Forms;

namespace UsbHidTestTool
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}

七、项目配置文件

UsbHidTestTool.csproj

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{YOUR-PROJECT-GUID}</ProjectGuid>
    <OutputType>WinExe</OutputType>
    <RootNamespace>UsbHidTestTool</RootNamespace>
    <AssemblyName>UsbHidTestTool</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Deployment" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Windows.Forms" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="HidDevice.cs" />
    <Compile Include="HidCapabilities.cs" />
    <Compile Include="UsbDeviceInfo.cs" />
    <Compile Include="HidEnumerator.cs" />
    <Compile Include="HexViewer.cs" />
    <Compile Include="MainForm.cs">
      <SubType>Form</SubType>
    </Compile>
    <Compile Include="MainForm.Designer.cs">
      <DependentUpon>MainForm.cs</DependentUpon>
    </Compile>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

参考代码 C#写的读取USB HID设备的测试软件 www.youwenfan.com/contentcsv/111905.html

八、使用说明

1. 功能特点

  • 枚举所有连接的USB HID设备
  • 显示设备详细信息(VID、PID、制造商、产品等)
  • 支持输入报告读取
  • 支持输出报告写入
  • 支持特征报告读写
  • 实时数据监控
  • 十六进制/ASCII显示
  • 数据包捕获和回放
  • 通信日志保存

2. 操作步骤

复制代码
1. 运行程序
2. 点击"刷新"按钮,查看设备列表
3. 选择要测试的HID设备
4. 点击"打开"按钮连接设备
5. 使用以下功能:
   - "读取输入报告":读取设备发送的数据
   - "发送输出报告":向设备发送数据
   - "读取特征报告":读取特征报告
   - "发送特征报告":写入特征报告
   - "开始监控":实时监控设备数据
   - "停止监控":停止监控
6. 在文本框中输入十六进制数据(如:01020304)
7. 查看通信日志

3. 数据格式

  • 十六进制:01 02 03 0401020304
  • 可包含报告ID(第一个字节)

4. 支持设备

  • 所有符合HID规范的USB设备
  • 键盘、鼠标、游戏手柄
  • HID传感器
  • 自定义HID设备
  • USB条码扫描器
  • 医疗设备等

九、高级功能扩展

1. 添加过滤功能

csharp 复制代码
// 在HidEnumerator.cs中添加
public static List<UsbDeviceInfo> EnumerateDevices(ushort? vendorId = null, ushort? productId = null)
{
    var allDevices = EnumerateDevices();
    
    if (vendorId.HasValue)
    {
        allDevices = allDevices.Where(d => d.VendorID == vendorId.Value).ToList();
    }
    
    if (productId.HasValue)
    {
        allDevices = allDevices.Where(d => d.ProductID == productId.Value).ToList();
    }
    
    return allDevices;
}

2. 添加数据解析器

csharp 复制代码
public class HidDataParser
{
    public static Dictionary<string, object> ParseMouseReport(byte[] data)
    {
        if (data.Length < 3) return null;
        
        return new Dictionary<string, object>
        {
            ["Button1"] = (data[0] & 0x01) != 0,
            ["Button2"] = (data[0] & 0x02) != 0,
            ["Button3"] = (data[0] & 0x04) != 0,
            ["X"] = (sbyte)data[1],
            ["Y"] = (sbyte)data[2]
        };
    }
    
    public static Dictionary<string, object> ParseKeyboardReport(byte[] data)
    {
        if (data.Length < 8) return null;
        
        var keys = new List<string>();
        for (int i = 2; i < 8; i++)
        {
            if (data[i] != 0)
            {
                keys.Add($"Key 0x{data[i]:X2}");
            }
        }
        
        return new Dictionary<string, object>
        {
            ["Modifiers"] = data[0],
            ["Reserved"] = data[1],
            ["Keys"] = keys
        };
    }
}

3. 添加脚本支持

csharp 复制代码
public class HidScriptRunner
{
    public void RunScript(string script)
    {
        // 支持简单的脚本命令
        // READ_INPUT: 读取输入
        // WRITE_OUTPUT 010203: 写入数据
        // DELAY 100: 延迟100ms
        // LOOP 10: 循环10次
    }
}

十、故障排除

问题 原因 解决方案
设备列表为空 无HID设备连接 连接HID设备
打开设备失败 权限不足 以管理员身份运行
读取失败 设备不支持该功能 检查设备能力
写入失败 数据格式错误 检查数据长度和格式
监控无数据 设备无数据发送 检查设备状态
相关推荐
三佛科技-187366133972 小时前
FT32F103C8AT7兼容GD32F103C8T632 位通用微控制器MCU,替代性分析
单片机·嵌入式硬件
iCxhust2 小时前
8086汇编 word ptr
汇编·单片机·嵌入式硬件·微机原理·8088单板机
嵌入式ZYXC2 小时前
第3篇:《面试题:I2C为什么要加上拉电阻?阻值怎么选?》
stm32·单片机·嵌入式硬件·面试·职场和发展
你疯了抱抱我3 小时前
【STM32】使用 STM32CubeMX 生成项目,LED测试;上位机:STM32F411CEU6
stm32·单片机·嵌入式硬件
今天的你比昨天进步了?5 小时前
单片机程序,keil可以正常编译,VScode编译报错处理
vscode·单片机·嵌入式硬件
linbaiwan6665 小时前
42V/50V/60V高耐压OVP保护芯片的应用——PW1600实测70V耐压
嵌入式硬件
嵌入式小站5 小时前
STM32 零基础可移植教程 24:SPI Flash 读数据,先从指定地址读几个字节
chrome·stm32·嵌入式硬件
崇山峻岭之间6 小时前
单片机汉字显示实验
单片机·嵌入式硬件