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/