1449 字
7 分钟
使用C#使用Windows的HID通信
Windows使用HID通信相对比较简单,HID都是通过PID、VID信息来查找连接的,相比于串口,几乎无变化,连接无需人工选择,十分方便,也不需要驱动。
下面上实例,PID为0x003f,VID为0x04D8,支持发送接收数据显示到UI,使用C#来编写,调用的是windows api(create file、read file、write file)。
本实例将HID接口分成3层,支持自动连接、断开状态通知,异步收发数据,单个数据包大小为64bytes(因为从设备的数据包设定为64bytes,保持一致)。
接口分为两层,第一层将create file、read file、write file封装,第二层再封装自动连接、异步收发。
HID.cs -> HIDInterface.cs -> 应用
注意,这里所有数据都是64bytes,但是有用数据并非这么多,所以设定为第一个数据为后面数据实际长度,若需要修改定义,请在HIDInterface.cs的Send与HidDataReceived函数中修改处理方法即可。
HID.cs
using System;using System.Collections.Generic;using System.Runtime.InteropServices;using System.IO;using Microsoft.Win32.SafeHandles;using System.Windows;
namespace HID_SIMPLE.HID{ public class ReportEventArgs : EventArgs { public byte ReportID { get; } public byte[] ReportData { get; }
public ReportEventArgs(byte reportID, byte[] reportData) { ReportID = reportID; ReportData = reportData; } }
public class HidDevice : IDisposable { private const int MAX_USB_DEVICES = 64; private readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); private bool _deviceOpened = false; private FileStream _hidDevice = null; private IntPtr _hubDevice;
private int _outputReportLength; public int OutputReportLength => _outputReportLength;
private int _inputReportLength; public int InputReportLength => _inputReportLength;
public event EventHandler<ReportEventArgs> DataReceived; public event EventHandler DeviceRemoved;
public HidDevice() { _deviceOpened = false; }
public HID_RETURN OpenDevice(ushort vendorId, ushort productId, string serial) { if (_deviceOpened) return HID_RETURN.DEVICE_OPENED;
var deviceList = GetHidDeviceList(); if (deviceList.Count == 0) return HID_RETURN.NO_DEVICE_CONECTED;
foreach (var devicePath in deviceList) { var deviceHandle = CreateFile(devicePath, DESIREDACCESS.GENERIC_READ | DESIREDACCESS.GENERIC_WRITE, 0, IntPtr.Zero, CREATIONDISPOSITION.OPEN_EXISTING, FLAGSANDATTRIBUTES.FILE_FLAG_OVERLAPPED, 0); if (deviceHandle != INVALID_HANDLE_VALUE) { var attributes = new HIDD_ATTRIBUTES(); IntPtr serialBuffer = Marshal.AllocHGlobal(512); HidD_GetAttributes(deviceHandle, out attributes); HidD_GetSerialNumberString(deviceHandle, serialBuffer, 512); string deviceSerial = Marshal.PtrToStringAuto(serialBuffer); Marshal.FreeHGlobal(serialBuffer);
if (attributes.VendorID == vendorId && attributes.ProductID == productId && deviceSerial.Contains(serial)) { IntPtr preparseData; HIDP_CAPS caps; HidD_GetPreparsedData(deviceHandle, out preparseData); HidP_GetCaps(preparseData, out caps); HidD_FreePreparsedData(preparseData);
_outputReportLength = caps.OutputReportByteLength; _inputReportLength = caps.InputReportByteLength;
_hidDevice = new FileStream(new SafeFileHandle(deviceHandle, false), FileAccess.ReadWrite, _inputReportLength, true); _deviceOpened = true;
BeginAsyncRead(); _hubDevice = deviceHandle; return HID_RETURN.SUCCESS; } } } return HID_RETURN.DEVICE_NOT_FIND; }
public void CloseDevice() { if (_deviceOpened) { _deviceOpened = false; _hidDevice.Close(); } }
private void BeginAsyncRead() { var inputBuff = new byte[InputReportLength]; _hidDevice.BeginRead(inputBuff, 0, InputReportLength, ReadCompleted, inputBuff); }
private void ReadCompleted(IAsyncResult asyncResult) { var readBuff = (byte[])asyncResult.AsyncState; try { _hidDevice.EndRead(asyncResult); var reportData = new byte[readBuff.Length - 1]; Array.Copy(readBuff, 1, reportData, 0, readBuff.Length - 1); var report = new ReportEventArgs(readBuff[0], reportData);
OnDataReceived(report);
if (_deviceOpened) BeginAsyncRead(); } catch { OnDeviceRemoved(EventArgs.Empty); CloseDevice(); } }
protected virtual void OnDataReceived(ReportEventArgs e) { DataReceived?.Invoke(this, e); }
protected virtual void OnDeviceRemoved(EventArgs e) { DeviceRemoved?.Invoke(this, e); }
public HID_RETURN Write(ReportEventArgs report) { if (!_deviceOpened) return HID_RETURN.WRITE_FAILD;
try { var buffer = new byte[_outputReportLength]; buffer[0] = report.ReportID;
int maxBufferLength = Math.Min(report.ReportData.Length, _outputReportLength - 1); Array.Copy(report.ReportData, 0, buffer, 1, maxBufferLength);
_hidDevice.Write(buffer, 0, OutputReportLength); return HID_RETURN.SUCCESS; } catch { OnDeviceRemoved(EventArgs.Empty); CloseDevice(); return HID_RETURN.NO_DEVICE_CONECTED; } }
public static List<string> GetHidDeviceList() { var deviceList = new List<string>(); Guid hidGuid = Guid.Empty; HidD_GetHidGuid(ref hidGuid);
IntPtr deviceInfoSet = SetupDiGetClassDevs(ref hidGuid, 0, IntPtr.Zero, DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE); if (deviceInfoSet != IntPtr.Zero) { var interfaceInfo = new SP_DEVICE_INTERFACE_DATA { cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA)) };
for (uint index = 0; index < MAX_USB_DEVICES; index++) { if (SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref hidGuid, index, ref interfaceInfo)) { int bufferSize = 0; SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref interfaceInfo, IntPtr.Zero, bufferSize, ref bufferSize, null);
IntPtr detailBuffer = Marshal.AllocHGlobal(bufferSize); var detail = new SP_DEVICE_INTERFACE_DETAIL_DATA { cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA)) }; Marshal.StructureToPtr(detail, detailBuffer, false);
if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref interfaceInfo, detailBuffer, bufferSize, ref bufferSize, null)) { deviceList.Add(Marshal.PtrToStringAuto((IntPtr)((int)detailBuffer + 4))); }
Marshal.FreeHGlobal(detailBuffer); } } }
SetupDiDestroyDeviceInfoList(deviceInfoSet); return deviceList; }
public void Dispose() { CloseDevice(); } }
public enum HID_RETURN { SUCCESS = 0, NO_DEVICE_CONECTED, DEVICE_NOT_FIND, DEVICE_OPENED, WRITE_FAILD, READ_FAILD }
#region Windows API Imports
// Add Windows API imports and structures here // Include all your PInvoke methods from original code #endregion}
HIDInterface.cs
using System;using System.Collections.Generic;using System.IO;using System.Text;using System.Threading;using System.ComponentModel;
namespace HID_SIMPLE.HID{ public class HIDInterface : IDisposable { // 消息类型 public enum MessagesType { Message, Error }
// 结果结构 public struct ResultString { public bool Result; public string Message; }
// 设备结构 public struct HidDevice { public UInt16 vID; public UInt16 pID; public string Serial; }
private HidDevice lowHidDevice = new HidDevice(); private Hid oSp = new Hid(); private static HIDInterface m_oInstance;
private bool bConnected = false;
public delegate void DelegateDataReceived(object sender, byte[] data); public delegate void DelegateStatusConnected(object sender, bool isConnect);
public event DelegateDataReceived DataReceived; public event DelegateStatusConnected StatusConnected;
private BackgroundWorker ReadWriteThread = new BackgroundWorker(); private bool ContinueConnectFlag = true;
// 构造函数 public HIDInterface() { m_oInstance = this; oSp.DataReceived = HidDataReceived; oSp.DeviceRemoved = HidDeviceRemoved; }
// 自动连接 public void AutoConnect(HidDevice hidDevice) { lowHidDevice = hidDevice; ContinueConnectFlag = true; ReadWriteThread.DoWork += ReadWriteThread_DoWork; ReadWriteThread.WorkerSupportsCancellation = true; ReadWriteThread.RunWorkerAsync(); }
// 停止自动连接 public void StopAutoConnect() { try { ContinueConnectFlag = false; Dispose(); } catch (Exception ex) { // 日志记录或进一步处理 Console.WriteLine(ex.Message); } }
// 连接设备 public bool Connect(HidDevice hidDevice) { ResultString result = new ResultString(); Hid.HID_RETURN hdrtn = oSp.OpenDevice(hidDevice.vID, hidDevice.pID, hidDevice.Serial);
if (hdrtn == Hid.HID_RETURN.SUCCESS) { bConnected = true; result.Result = true; result.Message = "Connect Success!"; RaiseEventConnectedState(result.Result); return true; }
bConnected = false; result.Result = false; result.Message = "Device Connect Error"; RaiseEventConnectedState(result.Result); return false; }
// 发送数据 public bool Send(byte[] byData) { byte[] sendtemp = new byte[byData.Length + 1]; sendtemp[0] = (byte)byData.Length; Array.Copy(byData, 0, sendtemp, 1, byData.Length);
Hid.HID_RETURN hdrtn = oSp.Write(new report(0, sendtemp)); return hdrtn == Hid.HID_RETURN.SUCCESS; }
// 发送字符串 public bool Send(string strData) { byte[] data = Encoding.Unicode.GetBytes(strData); return Send(data); }
// 断开连接 public void DisConnect() { bConnected = false; Thread.Sleep(200); oSp?.CloseDevice(); }
// 设备移除事件 private void HidDeviceRemoved(object sender, EventArgs e) { bConnected = false; ResultString result = new ResultString { Result = false, Message = "Device Removed" }; RaiseEventConnectedState(result.Result); oSp?.CloseDevice(); }
// 接收数据事件 private void HidDataReceived(object sender, report e) { try { byte[] buf = new byte[e.reportBuff[0]]; Array.Copy(e.reportBuff, 1, buf, 0, e.reportBuff[0]); RaiseEventDataReceived(buf); } catch (Exception ex) { ResultString result = new ResultString { Result = false, Message = "Receive Error: " + ex.Message }; RaiseEventConnectedState(result.Result); } }
// 事件通知:连接状态 private void RaiseEventConnectedState(bool isConnect) { StatusConnected?.Invoke(this, isConnect); }
// 事件通知:接收到数据 private void RaiseEventDataReceived(byte[] buf) { DataReceived?.Invoke(this, buf); }
// 释放资源 public void Dispose() { try { DisConnect(); oSp.DataReceived -= HidDataReceived; oSp.DeviceRemoved -= HidDeviceRemoved; ReadWriteThread.DoWork -= ReadWriteThread_DoWork; ReadWriteThread.CancelAsync(); ReadWriteThread.Dispose(); } catch (Exception ex) { // 错误处理或日志记录 Console.WriteLine(ex.Message); } }
// 自动连接线程 private void ReadWriteThread_DoWork(object sender, DoWorkEventArgs e) { while (ContinueConnectFlag) { try { if (!bConnected) { Connect(lowHidDevice); } Thread.Sleep(500); } catch (Exception ex) { // 错误处理 Console.WriteLine(ex.Message); } } } }}
调用示例:
HIDInterface hid = new HIDInterface();
connectStatusStruct connectStatus = new connectStatusStruct();
// 推送连接状态信息public delegate void isConnectedDelegate(bool isConnected);public isConnectedDelegate isConnectedFunc;
// 推送接收数据public delegate void PushReceiveDataDele(byte[] datas);public PushReceiveDataDele pushReceiveData;
// 初始化public void Initial(){ hid.StatusConnected = StatusConnected; hid.DataReceived = DataReceived;
HIDInterface.HidDevice hidDevice = new HIDInterface.HidDevice { vID = 0x04D8, pID = 0x003F, Serial = "" }; hid.AutoConnect(hidDevice);}
// 关闭public void Close(){ hid.StopAutoConnect();}
// 发送数据public bool SendBytes(byte[] data){ return hid.Send(data);}
// 数据接收回调public void DataReceived(object sender, byte[] e){ pushReceiveData?.Invoke(e);}
// 状态变化回调public void StatusConnected(object sender, bool isConnect){ connectStatus.curStatus = isConnect; if (connectStatus.curStatus == connectStatus.preStatus) // 相同状态不处理 return; connectStatus.preStatus = connectStatus.curStatus;
if (connectStatus.curStatus) { isConnectedFunc(true); } else { isConnectedFunc(false); }}
使用C#使用Windows的HID通信
https://hw.rscclub.website/posts/csharpusbhid/