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/
作者
杨月昌
发布于
2017-03-23
许可协议
CC BY-NC-SA 4.0