USB设备读写
从C盘搜索kernel32.dll、setupapi.dll、hid.dll,并复制到项目debug目录
加入USBHelper.cs,代码如下
public class USBHelper { bool result; string devicePathName; Guid guidHID = Guid.Empty; //指针 IntPtr hDevInfo; IntPtr deviceHandle = IntPtr.Zero; //CreateFile参数配置 public const uint GENERIC_READ = 0x80000000; public const uint GENERIC_WRITE = 0x40000000; public const uint FILE_SHARE_READ = 0x00000001; public const uint FILE_SHARE_WRITE = 0x00000002; public const uint FILE_FLAG_OVERLAPPED = 0x40000000; public const int OPEN_EXISTING = 3; #region Structs public enum DIGCF { DIGCF_DEFAULT = 0x1, DIGCF_PRESENT = 0x2, DIGCF_ALLCLASSES = 0x4, DIGCF_PROFILE = 0x8, DIGCF_DEVICEINTERFACE = 0x10 } //SetupDiEnumDeviceInterfaces 识别出来的SP_DEVICE_INTERFACE_DATA结构,该结构标识满足搜索参数的接口 public struct SP_DEVICE_INTERFACE_DATA { public int cbSize; //SP_DEVICE_INTERFACE_DATA结构的大小 public Guid interfaceClassGuid; //设备接口所属的类的GUID public int flags; //接口转态标记 public int reserved; //保留,不做使用 } [StructLayout(LayoutKind.Sequential)] public class SP_DEVINFO_DATA { public int cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA)); public Guid classGuid = Guid.Empty; // temp public int devInst = 0; // dumy public int reserved = 0; } [StructLayout(LayoutKind.Sequential, Pack = 2)] internal struct SP_DEVICE_INTERFACE_DETAIL_DATA { internal int cbSize; internal short devicePath; } //HidD_GetAttributes的调用者使用此结构来对比查找设备 public unsafe struct HIDD_ATTRIBUTES { public int Size; //指定HIDD_ATTRIBUTES结构的大小(以字节为单位) public ushort VendorID; //指定HID设备的供应商ID( VID ) public ushort ProductID; //指定HID设备的产品ID( PID ) public ushort VersionNumber; //指定HIDClass设备的制造商版本号 } //自定义的结构体,用来存放自己要操作的设备信息 public unsafe struct my_usb_id { public ushort my_vid; public ushort my_Pid; public ushort my_number; } [StructLayout(LayoutKind.Sequential)] public unsafe struct HIDP_CAPS { public ushort UsagePage; //指定顶级集合的使用情况 public uint Usage; //指定顶级集合的 使用ID public ushort InputReportByteLength; //指定所有输入报告的最大大小(以字节为单位) public ushort OutputReportByteLength; //指定所有输出报告的最大大小(以字节为单位) public ushort FeatureReportByteLength; //指定所有功能报告的最大长度(以字节为单位) [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)] //保留供内部系统使用数组 public ushort NumberLinkCollectionNodes; //指定的数量HIDP_LINK_COLLECTION_NODE了为这个顶级集合返回的结构HidP_GetLinkCollectionNodes public ushort NumberInputButtonCaps; //指定HidP_GetButtonCaps返回的输入HIDP_BUTTON_CAPS结构的数量 public ushort NumberInputValueCaps; //指定HidP_GetValueCaps返回的输入HIDP_VALUE_CAPS结构的数量 public ushort NumberInputDataIndices; //指定分配给所有输入报告中的按钮和值的数据索引数 public ushort NumberOutputButtonCaps; //指定HidP_GetButtonCaps返回的输出HIDP_BUTTON_CAPS结构的数量 public ushort NumberOutputValueCaps; //指定HidP_GetValueCaps返回的输出HIDP_VALUE_CAPS结构的数量 public ushort NumberOutputDataIndices; //指定分配给所有输出报告中的按钮和值的数据索引数 public ushort NumberFeatureButtonCaps; //指定HidP_GetButtonCaps返回的功能HIDP_BUTTONS_CAPS结构的总数 public ushort NumberFeatureValueCaps; //指定HidP_GetValueCaps返回的功能HIDP_VALUE_CAPS结构的总数 public ushort NumberFeatureDataIndices; //指定分配给所有要素报告中的按钮和值的数据索引数 } /*异步通信用,internal是错误码,internalHigh是传输字节,这个两个是IO操作完成后需要填写的内容*/ [StructLayout(LayoutKind.Sequential)] public struct OVERLAPPED { public IntPtr Internal; //I/O请求的状态代码 public IntPtr InternalHigh; //传输I/O请求的字节数 public int Offset; //文件读写起始位置 public int OffsetHigh; //地址偏移量 public IntPtr hEvent; //操作完成时系统将设置为信号状态的事件句柄 } #endregion #region WindosApi //获得USB设备的GUID [DllImport("hid.dll")] public static extern void HidD_GetHidGuid(ref Guid HidGuid); //获得一个包含全部HID信息的结构数组的指针,具体配置看函数说明:https://docs.microsoft.com/zh-cn/windows/desktop/api/setupapi/nf-setupapi-setupdigetclassdevsw [DllImport("setupapi.dll", SetLastError = true)] public static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, DIGCF Flags); //该结构用于识别一个HID设备接口,获取设备,true获取到 [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr hDevInfo, IntPtr devInfo, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData); // 获得一个指向该设备的路径名,接口的详细信息 必须调用两次 第1次返回长度 第2次获取数据 [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr deviceInfoSet, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, IntPtr deviceInterfaceDetailData, int deviceInterfaceDetailDataSize, ref int requiredSize, SP_DEVINFO_DATA deviceInfoData); [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet); //获取设备文件(获取句柄) [DllImport("kernel32.dll", SetLastError = true)] //根据要求可在下面设定参数,具体参考参数说明:https://docs.microsoft.com/zh-cn/windows/desktop/api/fileapi/nf-fileapi-createfilea private static extern IntPtr CreateFile ( string lpFileName, // file name 文件名 uint dwDesiredAccess, // access mode 访问模式 uint dwShareMode, // share mode 共享模式 uint lpSecurityAttributes, // SD 安全属性 uint dwCreationDisposition, // how to create 如何创建 uint dwFlagsAndAttributes, // file attributes 文件属性 uint hTemplateFile // handle to template file 模板文件的句柄 ); [DllImport("Kernel32.dll", SetLastError = true)] //接收函数DLL private static extern bool ReadFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToRead, ref uint lpNumberOfBytesRead, IntPtr lpOverlapped); [DllImport("kernel32.dll", SetLastError = true)] //发送数据DLL public static extern Boolean WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, ref uint nNumberOfBytesWrite, IntPtr lpOverlapped); [DllImport("hid.dll")] /*HidDeviceObject:指定顶级集合的打开句柄 Attributes:指向调用者分配的HIDD_ATTRIBUTES结构的指针,该结构返回由HidDeviceObject指定的集合的属性*/ private static extern Boolean HidD_GetAttributes(IntPtr hidDeviceObject, out HIDD_ATTRIBUTES HIDD_ATTRIBUTES); /**/ [DllImport("hid.dll")] /*HidP_GetCaps返回一个顶级集合的 HIDP_CAPS结构,获取设备具体信息,这里暂时用不上 PreparsedData:指向顶级集合的预分析数据的指针; Capabilities:指向调用程序分配的缓冲区的指针,该缓冲区用于返回集合的HIDP_CAPS结构*/ private static extern uint HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities); [DllImport("hid.dll")] private static extern Boolean HidD_GetPreparsedData(IntPtr hidDeviceObject, out IntPtr PreparsedData); //释放设备 [DllImport("hid.dll")] static public extern bool HidD_FreePreparsedData(ref IntPtr PreparsedData); //关闭访问设备句柄,结束进程的时候把这个加上保险点 [DllImport("kernel32.dll")] static public extern int CloseHandle(IntPtr hObject); //查看数据传输异常函数 [DllImport("kernel32.dll", EntryPoint = "GetProcAddress", SetLastError = true)] public static extern IntPtr GetProcAddress(int hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName); /*监听异步通信函数*/ [DllImport("Kernel32.dll")] public static extern unsafe long WaitForSingleObject(IntPtr hHandle, long dwMilliseconds); [DllImport("Kernel32.dll")] public static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, [MarshalAs(UnmanagedType.LPStr)] string lpName); #endregion /// <summary> /// /// </summary> /// <param name="vendorID"></param> /// <param name="productID"></param> /// <returns>HidHandle</returns> public IntPtr FindDevice(ushort vendorID, ushort productID) { int index = 0; //获取USB设备的GUID HidD_GetHidGuid(ref guidHID); //Console.WriteLine(" GUID_HID = "+ guidHID); //输出guid信息调试用 //获取系统中存在的所有设备的列表,这些设备已从存储卷设备接口类启用了接口 hDevInfo = SetupDiGetClassDevs(ref guidHID, 0, IntPtr.Zero, DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE); int bufferSize = 0; ArrayList HIDUSBAddress = new ArrayList(); while (true) { //获取设备,true获取到 SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = new SP_DEVICE_INTERFACE_DATA(); DeviceInterfaceData.cbSize = Marshal.SizeOf(DeviceInterfaceData); for (int i = 0; i < 3; i++) { //识别HID设备接口,获取设备,返回true成功 result = SetupDiEnumDeviceInterfaces(hDevInfo, IntPtr.Zero, ref guidHID, (UInt32)index, ref DeviceInterfaceData); } //Console.WriteLine(" 识别HID接口\t"+result); //识别接口打印信息查看 //第一次调用出错,但可以返回正确的Size SP_DEVINFO_DATA strtInterfaceData = new SP_DEVINFO_DATA(); //获得一个指向该设备的路径名,接口的详细信息 必须调用两次 第1次返回路径长度 result = SetupDiGetDeviceInterfaceDetail(hDevInfo, ref DeviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, strtInterfaceData); //第二次调用传递返回值,调用即可成功 , 第2次获取路径数据 IntPtr detailDataBuffer = Marshal.AllocHGlobal(bufferSize); SP_DEVICE_INTERFACE_DETAIL_DATA detailData = new SP_DEVICE_INTERFACE_DETAIL_DATA(); detailData.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA)); Marshal.StructureToPtr(detailData, detailDataBuffer, false); result = SetupDiGetDeviceInterfaceDetail(hDevInfo, ref DeviceInterfaceData, detailDataBuffer, bufferSize, ref bufferSize, strtInterfaceData); if (result == false) break; //获取设备路径访 IntPtr pdevicePathName = (IntPtr)((int)detailDataBuffer + 4); devicePathName = Marshal.PtrToStringAuto(pdevicePathName); HIDUSBAddress.Add(devicePathName); //Console.WriteLine(" Get_DvicePathName = "+ devicePathName); //打印路径信息,调试用 //连接USB设备文件 deviceHandle = CreateFile(devicePathName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);//FILE_FLAG_OVERLAPPED if (deviceHandle != IntPtr.Zero) //设备连接成功 { HIDD_ATTRIBUTES HIDD_ATTRIBUTE = new HIDD_ATTRIBUTES(); //handle是CreateFile函数返回一个有效的设备操作句柄,HIDD_ATTRIBUTES是函数返回的结构体信息(VID PID 设备号) bool sel = HidD_GetAttributes(deviceHandle, out HIDD_ATTRIBUTE); if (vendorID == HIDD_ATTRIBUTE.VendorID && productID == HIDD_ATTRIBUTE.ProductID) return deviceHandle; } index++; } return IntPtr.Zero; } //写设备数据 /// <summary> /// /// </summary> /// <param name="data"></param> /// <returns></returns> public int Write(byte[] data) { //OVERLAPPED Overlapped = new OVERLAPPED(); //Overlapped.hEvent = IntPtr.Zero; //Overlapped.Offset = 0; //Overlapped.OffsetHigh = 0; //IntPtr overLappedPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Overlapped)); //Marshal.StructureToPtr(Overlapped, overLappedPtr, true); byte[] sendData = new byte[65]; sendData[0] = 0x00;//开头处加入设备端点 data.CopyTo(sendData, 1); uint read = 0; bool isread = WriteFile(deviceHandle, sendData, (uint)sendData.Length, ref read, IntPtr.Zero); if (isread == false) return Marshal.GetLastWin32Error(); return 0; } public bool Readata(ref byte[] buffer)//USB异步接收数据 { //初始化Overlapped //OVERLAPPED overlap = new OVERLAPPED(); //overlap.Offset = 0; //overlap.OffsetHigh = 0; //overlap.hEvent = CreateEvent(IntPtr.Zero, false, false, null);//创建事件对象 byte[] reBuffer = new byte[65]; uint dwRead = 0; //读设备 bool re = ReadFile(deviceHandle, reBuffer, (uint)reBuffer.Length, ref dwRead, IntPtr.Zero); if (re) {//去掉开头处的设备端点 for (int i = 0; i < buffer.Length; i++) { buffer[i] = reBuffer[i + 1]; } } // long cc=WaitForSingleObject(overlap.hEvent, 5000); return re; } /* 释放关闭USB设备 */ public void Dispost() { //释放设备资源(hDevInfo是SetupDiGetClassDevs获取的) SetupDiDestroyDeviceInfoList(hDevInfo); //关闭连接(HidHandle是Create的时候获取的) CloseHandle(deviceHandle); hDevInfo = IntPtr.Zero; deviceHandle = IntPtr.Zero; } }
调用案例如下
private static void TestUSBHelper() { var usbHelper = new USBHelper(); try { int errCode = 0; byte[] byteArr = new byte[] { 0x01, 0x02 }; //根据vid和pid查找设备 IntPtr handle = usbHelper.FindDevice(0x0123, 0X0002); if (handle != IntPtr.Zero) { errCode = usbHelper.Write(byteArr); if (errCode == 0) { byte[] byteArr = new byte[5];//长度根据自定义协议定 bool success = usbHelper.Readata(ref byteArr); if (success) Console.WriteLine("成功"); else Console.WriteLine("失败"); } } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { usbHelper.Dispost(); } }