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 04或01020304 - 可包含报告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设备 |
| 打开设备失败 | 权限不足 | 以管理员身份运行 |
| 读取失败 | 设备不支持该功能 | 检查设备能力 |
| 写入失败 | 数据格式错误 | 检查数据长度和格式 |
| 监控无数据 | 设备无数据发送 | 检查设备状态 |