基于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设备
打开设备失败 权限不足 以管理员身份运行
读取失败 设备不支持该功能 检查设备能力
写入失败 数据格式错误 检查数据长度和格式
监控无数据 设备无数据发送 检查设备状态
相关推荐
u1521096484912 天前
S.S.Audio PRO A2音频隔离器
嵌入式硬件·音视频·实时音视频·视频编解码·视频
zd84510150013 天前
RS485 总线详解
单片机·嵌入式硬件
半条-咸鱼13 天前
【STM32】I2C协议原理、HAL读写与OLED显示操作
嵌入式硬件·c·信息与通信
wohoo_wangzi13 天前
苏州晟雅泰电子:关于W25Q128JVSIQ这个芯片物料的参数,规格及应用领域
嵌入式硬件
科芯创展13 天前
1A,1MHz,30VIN,XZ4115,降压恒流LED驱动芯片
单片机·嵌入式硬件
集芯微电科技有限公司13 天前
四通道2A输出集成功率电感降压模块专为紧凑型方案设计
人工智能·单片机·嵌入式硬件·生成对抗网络·计算机外设
踏着七彩祥云的小丑13 天前
嵌入式测试学习第 37 天:异常场景测试:断电、拔插、干扰、非法指令
单片机·嵌入式硬件·学习
望眼欲穿的程序猿13 天前
读取芯片内部温度传感器
嵌入式硬件·rust
望眼欲穿的程序猿13 天前
ADC 模拟电压采集
嵌入式硬件·rust
IT方大同13 天前
(嵌入式操作系统)信号量
嵌入式硬件·c#