創建C# USB hid通訊類
下面是應用到WIN32 API:
1. 讀取Hid設備全局id
[DllImport("hid.dll")]
private static extern void HidD_GetHidGuid(ref Guid HidGuid);
2. 取得一個包含所有HID接口信息集合的句柄
[DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, DIGCF Flags);
3.遍歷信息集合,得到每個設備的接口信息
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, IntPtr deviceInfoData, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
4. 取得接口詳細信息:第一次讀取錯誤,但可以取得信息緩沖區的大小
SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null);
5. 取得接口詳細信息: 第二次可以讀取內容(內容包括VID,PID)
SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null);
6. 利用上一步讀取的設備路徑信息,使用CreateFile打開設備
[DllImport("kernel32.dll", SetLastError = true)]
protected static extern SafeFileHandle CreateFile(string strName, uint nAccess, uint nShareMode, uint lpSecurity, uint nCreationFlags, uint nAttributes, uint lpTemplate);
7. 根據文件句柄,讀取設備屬性信息
[DllImport("hid.dll")]
private static extern Boolean HidD_GetAttributes(SafeFileHandle hidDeviceObject, out HIDD_ATTRIBUTES attributes);
8. 根據屬性信息, 判斷VID和PID和下位機設備相同,得到此設備
根據得到的匹配設備, 得到准備數據
[DllImport("hid.dll")]
private static extern Boolean HidD_GetPreparsedData(SafeFileHandle hidDeviceObject, out IntPtr PreparsedData);
[DllImport("hid.dll")]
private static extern Boolean HidD_FreePreparsedData(IntPtr PreparsedData);
[DllImport("hid.dll")]
private static extern uint HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities);
9.創建文件流
hidDevice = new FileStream(device, FileAccess.ReadWrite, inputReportLength, true);
10. 根據文件流讀寫數據
從流中讀取字節塊並將該數據寫入給定緩沖區中。
public override int Read(byte[] array, int offset, int count);
使用從緩沖區讀取的數據將字節塊寫入該流。
public override void Write(byte[] array, int offset, int count);
注意內容:
發送內容的時候需要發送outputReportLength長度的內容,其中第一個字節是reportid=0x00,發送內容從第二個字節開始,這里的獲取到下位機設置的outputReportLength為65
接收下位機內容時,下位機需要發inputReportLength長度的內容, 這里的長度為64
核心源碼:
public class Hid : object { protected const Int32 WAIT_TIMEOUT = 0X102; protected const Int32 WAIT_OBJECT_0 = 0; protected static string strDevicePath = ""; public UInt16 VID = 0x0483; public UInt16 PID = 0x5750; private IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); private const int MAX_USB_DEVICES = 64; private bool deviceOpened = false; private FileStream hidDevice = null; int outputReportLength;//輸出報告長度,包刮一個字節的報告ID public int OutputReportLength { get { return outputReportLength; } } int inputReportLength;//輸入報告長度,包刮一個字節的報告ID public int InputReportLength { get { return inputReportLength; } } private Guid device_class; private IntPtr usb_event_handle; private IntPtr handle; private SafeFileHandle device; private frmMain _main; private Thread _readThread; private int _openDeviceFlag = 0; public int OpenDeviceFlag { get { return _openDeviceFlag; } set { _openDeviceFlag = value; } } public Hid(frmMain main) { device_class = HIDGuid; _main = main; } /// <summary> /// Provides details about a single USB device /// </summary> [StructLayout(LayoutKind.Sequential, Pack = 1)] protected struct DeviceInterfaceData { public int Size; public Guid InterfaceClassGuid; public int Flags; public IntPtr Reserved; // should correspond to ULONG_PTR but was an int } private static string GetDevicePath(IntPtr hInfoSet, ref DeviceInterfaceData oInterface) { DeviceInterfaceDetailData oDetail = new DeviceInterfaceDetailData(); // Size workaround if (IntPtr.Size == 8) oDetail.Size = 8; else oDetail.Size = 5; Console.WriteLine("Size of struct: {0}", Marshal.SizeOf(oDetail)); // 4 + 256 = 260 uint nRequiredSize = 0; // Error 0 if (!SetupDiGetDeviceInterfaceDetail(hInfoSet, ref oInterface, IntPtr.Zero, 0, ref nRequiredSize, IntPtr.Zero)) // Error 122 - ERROR_INSUFFICIENT_BUFFER (not a problem, just used to set nRequiredSize) if (SetupDiGetDeviceInterfaceDetail(hInfoSet, ref oInterface, ref oDetail, nRequiredSize, ref nRequiredSize, IntPtr.Zero)) return oDetail.DevicePath; // Error 1784 - ERROR_INVALID_USER_BUFFER (unless size=5 on 32bit, size=8 on 64bit) return null; } /// <summary> /// 打開指定信息的設備 /// </summary> /// <param name="vID">設備的vID</param> /// <param name="pID">設備的pID</param> /// <param name="serial">設備的serial</param>,string serial /// <returns></returns> public HID_RETURN OpenDevice(UInt16 vID,UInt16 pID) { // IntPtr hInfoSet = SetupDiGetClassDevs(ref gHid, null, IntPtr.Zero, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); if (deviceOpened == false) { //獲取連接的HID列表 List<string> deviceList = new List<string>(); GetHidDeviceList(ref deviceList); if (deviceList.Count == 0) return HID_RETURN.NO_DEVICE_CONECTED; for (int i = 0; i < deviceList.Count; i++) { device = CreateFile(deviceList[i], DESIREDACCESS.GENERIC_READ | DESIREDACCESS.GENERIC_WRITE, 0, 0, CREATIONDISPOSITION.OPEN_EXISTING, FLAGSANDATTRIBUTES.FILE_FLAG_OVERLAPPED, 0); if (!device.IsInvalid) { // strDevicePath = GetDevicePath(hInfoSet, ref oInterface); HIDD_ATTRIBUTES attributes; //IntPtr serialBuff = Marshal.AllocHGlobal(512); HidD_GetAttributes(device, out attributes); //HidD_GetSerialNumberString(device, serialBuff, 512); //string deviceStr = Marshal.PtrToStringAuto(serialBuff); //Marshal.FreeHGlobal(serialBuff); if (attributes.VendorID == vID && attributes.ProductID == pID) // && deviceStr == serial { IntPtr preparseData; HIDP_CAPS caps; HidD_GetPreparsedData(device, out preparseData); HidP_GetCaps(preparseData, out caps); HidD_FreePreparsedData(preparseData); outputReportLength = caps.OutputReportByteLength; inputReportLength = caps.InputReportByteLength; hidDevice = new FileStream (device, FileAccess.ReadWrite, inputReportLength, true); deviceOpened = true; //BeginAsyncRead(); Guid gHid = HIDGuid; IntPtr hInfoSet = SetupDiGetClassDevs(ref gHid, null, IntPtr.Zero, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); DeviceInterfaceData oInterface = new DeviceInterfaceData(); strDevicePath = GetDevicePath(hInfoSet, ref oInterface); return HID_RETURN.SUCCESS; } } } return HID_RETURN.DEVICE_NOT_FIND; } else return HID_RETURN.DEVICE_OPENED; } /// <summary> /// 關閉打開的設備 /// </summary> public void CloseDevice() { if (deviceOpened == true) { hidDevice.Close(); deviceOpened = false; } } /// <summary> /// 開始一次異步讀 /// </summary> private void BeginAsyncRead() { byte[] inputBuff = new byte[InputReportLength]; hidDevice.BeginRead(inputBuff, 0, InputReportLength, new AsyncCallback(ReadCompleted), inputBuff); } /// <summary> /// 異步讀取結束,發出有數據到達事件 /// </summary> /// <param name="iResult">這里是輸入報告的數組</param> private void ReadCompleted(IAsyncResult iResult) { byte[] readBuff = (byte[])(iResult.AsyncState); try { hidDevice.EndRead(iResult);//讀取結束,如果讀取錯誤就會產生一個異常 byte[] reportData= new byte[readBuff.Length - 1]; for (int i = 1; i < readBuff.Length; i++) reportData[i - 1] = readBuff[i]; Report e = new Report(readBuff[0], reportData); //OnDataReceived(e); //發出數據到達消息 Define.cmdRecv.ProcData(reportData,reportData.Length); BeginAsyncRead();//啟動下一次讀操作 } catch (IOException e)//讀寫錯誤,設備已經被移除 { EventArgs ex = new EventArgs(); OnDeviceRemoved(ex);//發出設備移除消息 CloseDevice(); } } /// <summary> /// 事件:數據到達,處理此事件以接收輸入數據 /// </summary> public event EventHandler<Report> DataReceived; protected virtual void OnDataReceived(Report e) { if(DataReceived != null) DataReceived(this, e); } /// <summary> /// 事件:設置連接 /// </summary> public event EventHandler DeviceArrived; protected virtual void OnDeviceArrived( EventArgs e ) { if (DeviceArrived != null) DeviceArrived(this, e); } /// <summary> /// 事件:設備斷開 /// </summary> public event EventHandler DeviceRemoved; protected virtual void OnDeviceRemoved(EventArgs e) { if (DeviceRemoved != null) DeviceRemoved(this, e); } /// <summary> /// 發送report命令 /// </summary> /// <param name="buffer"></param> /// <returns></returns> public string Write(Report r) { byte[] buffer = null; if (deviceOpened) { try { buffer = new byte[outputReportLength]; buffer[0] = r.reportID; int maxBufferLength = 0; if (r.reportBuff.Length < outputReportLength - 1) maxBufferLength = r.reportBuff.Length; else maxBufferLength = outputReportLength - 1; for (int i = 1; i < maxBufferLength; i++) buffer[i] = r.reportBuff[i - 1]; hidDevice.Write(buffer, 0, OutputReportLength); //buffer= DataRead(); //Hid.ByteToHexString(buffer); } catch { EventArgs ex = new EventArgs(); OnDeviceRemoved(ex);//發出設備移除消息 CloseDevice(); throw; } } return ByteToHexString(buffer); } /// <summary> /// 發送字節數組命令 /// </summary> /// <param name="buffer"></param> /// <returns></returns> public bool Write(Byte[] buf) { if (deviceOpened) { try { hidDevice.Write(buf, 0, OutputReportLength); if (_readThread == null) { _readThread = new Thread(new ThreadStart(DataRead)); _readThread.IsBackground = true; _readThread.Start(); } //buffer = DataRead(); //Hid.ByteToHexString(buffer); return true; } catch { EventArgs ex = new EventArgs(); OnDeviceRemoved(ex);//發出設備移除消息 CloseDevice(); return false; } } else { return false; } } private void DataRead() { try { InputReportViaInterruptTransfer report = new InputReportViaInterruptTransfer(); bool foundMyDevice = false; bool result = false; byte[] readBuffer = new Byte[OutputReportLength]; report.Read(device, ref foundMyDevice, ref readBuffer, ref result); if (result == false) { _main.ProgressBarEnd(); MessageBox.Show("更新超時!"); } _readThread = null; } catch(Exception ex) { _readThread = null; } } internal class InputReportViaInterruptTransfer : ReportIn { internal Boolean readyForOverlappedTransfer; // initialize to false /// <summary> /// closes open handles to a device. /// </summary> /// /// <param name="hidHandle"> the handle for learning about the device and exchanging Feature reports. </param> /// <param name="readHandle"> the handle for reading Input reports from the device. </param> /// <param name="writeHandle"> the handle for writing Output reports to the device. </param> internal void CancelTransfer(SafeFileHandle hidHandle, SafeFileHandle readHandle, SafeFileHandle writeHandle, IntPtr eventObject) { try { // *** // API function: CancelIo // Purpose: Cancels a call to ReadFile // Accepts: the device handle. // Returns: True on success, False on failure. // *** CancelIo(readHandle); //Console.WriteLine("************ReadFile error*************"); //String functionName = "CancelIo"; //Console.WriteLine(MyDebugging.ResultOfAPICall(functionName)); //Console.WriteLine(""); // The failure may have been because the device was removed, // so close any open handles and // set myDeviceDetected=False to cause the application to // look for the device on the next attempt. if ((!(hidHandle.IsInvalid))) { hidHandle.Close(); } if ((!(readHandle.IsInvalid))) { readHandle.Close(); } if ((!(writeHandle.IsInvalid))) { writeHandle.Close(); } } catch (Exception ex) { //DisplayException(MODULE_NAME, ex); throw; } } /// <summary> /// Creates an event object for the overlapped structure used with /// ReadFile. Called before the first call to ReadFile. /// </summary> /// /// <param name="hidOverlapped"> the overlapped structure </param> /// <param name="eventObject"> the event object </param> internal void PrepareForOverlappedTransfer(ref NativeOverlapped hidOverlapped, ref IntPtr eventObject) { try { // *** // API function: CreateEvent // Purpose: Creates an event object for the overlapped structure used with ReadFile. // Accepts: // A security attributes structure or IntPtr.Zero. // Manual Reset = False (The system automatically resets the state to nonsignaled // after a waiting thread has been released.) // Initial state = False (not signaled) // An event object name (optional) // Returns: a handle to the event object // *** eventObject = CreateEvent(IntPtr.Zero, false, false, ""); // Console.WriteLineLine(MyDebugging.ResultOfAPICall("CreateEvent")) // Set the members of the overlapped structure. hidOverlapped.OffsetLow = 0; hidOverlapped.OffsetHigh = 0; hidOverlapped.EventHandle = eventObject; readyForOverlappedTransfer = true; } catch (Exception ex) { //DisplayException(MODULE_NAME, ex); throw; } } /// <summary> /// reads an Input report from the device using interrupt transfers. /// </summary> /// /// <param name="hidHandle"> the handle for learning about the device and exchanging Feature reports. </param> /// <param name="readHandle"> the handle for reading Input reports from the device. </param> /// <param name="writeHandle"> the handle for writing Output reports to the device. </param> /// <param name="myDeviceDetected"> tells whether the device is currently attached. </param> /// <param name="inputReportBuffer"> contains the requested report. </param> /// <param name="success"> read success </param> internal override void Read(SafeFileHandle readHandle, ref Boolean myDeviceDetected, ref Byte[] inputReportBuffer, ref Boolean success) { IntPtr eventObject = CreateEvent(IntPtr.Zero, false, false, ""); //IntPtr.Zero; NativeOverlapped HidOverlapped = new NativeOverlapped(); Int32 numberOfBytesRead = 0; Int32 result = 0; try { // If it's the first attempt to read, set up the overlapped structure for ReadFile. if (readyForOverlappedTransfer == false) { PrepareForOverlappedTransfer(ref HidOverlapped, ref eventObject); } // *** // API function: ReadFile // Purpose: Attempts to read an Input report from the device. // Accepts: // A device handle returned by CreateFile // (for overlapped I/O, CreateFile must have been called with FILE_FLAG_OVERLAPPED), // A pointer to a buffer for storing the report. // The Input report length in bytes returned by HidP_GetCaps, // A pointer to a variable that will hold the number of bytes read. // An overlapped structure whose hEvent member is set to an event object. // Returns: the report in ReadBuffer. // The overlapped call returns immediately, even if the data hasn't been received yet. // To read multiple reports with one ReadFile, increase the size of ReadBuffer // and use NumberOfBytesRead to determine how many reports were returned. // Use a larger buffer if the application can't keep up with reading each report // individually. // *** success = ReadFile(readHandle, inputReportBuffer, inputReportBuffer.Length, ref numberOfBytesRead, ref HidOverlapped); if (!success) { Console.WriteLine("waiting for ReadFile"); // API function: WaitForSingleObject // Purpose: waits for at least one report or a timeout. // Used with overlapped ReadFile. // Accepts: // An event object created with CreateEvent // A timeout value in milliseconds. // Returns: A result code. result = WaitForSingleObject(eventObject, 3000); //eventObject //result = GetOverlappedResult(readHandle.DangerousGetHandle(), ref HidOverlapped, ref numberOfBytesRead, false); //if (result != 0) // success = true; // Find out if ReadFile completed or timeout. switch (result) { case (System.Int32)WAIT_OBJECT_0: // ReadFile has completed success = true; Console.WriteLine("ReadFile completed successfully."); Define.cmdRecv.ProcData(inputReportBuffer, inputReportBuffer.Length); break; case WAIT_TIMEOUT: // Cancel the operation on timeout // CancelTransfer(hidHandle, readHandle, writeHandle, eventObject); Console.WriteLine("Readfile timeout"); success = false; myDeviceDetected = false; break; default: // Cancel the operation on other error. //CancelTransfer(hidHandle, readHandle, writeHandle, eventObject); Console.WriteLine("Readfile undefined error"); success = false; myDeviceDetected = false; break; } } } catch (Exception ex) { ////DisplayException(MODULE_NAME, ex); throw; } } } internal abstract class ReportIn { /// <summary> /// Each class that handles reading reports defines a Read method for reading /// a type of report. Read is declared as a Sub rather /// than as a Function because asynchronous reads use a callback method /// that can access parameters passed by ByRef but not Function return values. /// </summary> internal abstract void Read(SafeFileHandle readHandle, ref Boolean myDeviceDetected, ref Byte[] readBuffer, ref Boolean success); } /// <summary> /// 獲取所有連接的hid的設備路徑 /// </summary> /// <returns>包含每個設備路徑的字符串數組</returns> public static void GetHidDeviceList(ref List<string> deviceList) { Guid hUSB = Guid.Empty; uint index = 0; deviceList.Clear(); // 取得hid設備全局id HidD_GetHidGuid(ref hUSB); //取得一個包含所有HID接口信息集合的句柄 IntPtr hidInfoSet = SetupDiGetClassDevs(ref hUSB, 0, IntPtr.Zero, DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE); if (hidInfoSet != IntPtr.Zero) { SP_DEVICE_INTERFACE_DATA interfaceInfo = new SP_DEVICE_INTERFACE_DATA(); interfaceInfo.cbSize = Marshal.SizeOf(interfaceInfo); //查詢集合中每一個接口 for (index = 0; index < MAX_USB_DEVICES; index++) { //得到第index個接口信息 if (SetupDiEnumDeviceInterfaces(hidInfoSet, IntPtr.Zero, ref hUSB, index, ref interfaceInfo)) { int buffsize = 0; // 取得接口詳細信息:第一次讀取錯誤,但可以取得信息緩沖區的大小 SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null); //構建接收緩沖 IntPtr pDetail = Marshal.AllocHGlobal(buffsize); SP_DEVICE_INTERFACE_DETAIL_DATA detail = new SP_DEVICE_INTERFACE_DETAIL_DATA(); detail.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA)); Marshal.StructureToPtr(detail, pDetail, false); if (SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, pDetail, buffsize, ref buffsize, null)) { deviceList.Add(Marshal.PtrToStringAuto((IntPtr)((int)pDetail + 4))); } Marshal.FreeHGlobal(pDetail); } } } SetupDiDestroyDeviceInfoList(hidInfoSet); //return deviceList.ToArray(); } public void ParseMessages( ref Message m ) { if (m.Msg == DEVICE_FLAG.WM_DEVICECHANGE) // we got a device change message! A USB device was inserted or removed { switch (m.WParam.ToInt32()) // Check the W parameter to see if a device was inserted or removed { case DEVICE_FLAG.DEVICE_ARRIVAL: // inserted Console.WriteLine("ParseMessages 設備連接"); if (DeviceArrived != null) { DeviceArrived(this, new EventArgs()); } CheckDevicePresent(); break; case DEVICE_FLAG.DEVICE_REMOVECOMPLETE: // removed Console.WriteLine("ParseMessages 設備拔除"); if (DeviceRemoved != null) { DeviceRemoved(this, new EventArgs()); } CheckDevicePresent(); break; } } } public void RegisterHandle( IntPtr Handle ) { usb_event_handle = RegisterForUsbEvents(Handle, device_class); this.handle = Handle; //Check if the device is already present. CheckDevicePresent(); } public bool CheckDevicePresent() { HID_RETURN hid_ret; string sSerial=""; try { hid_ret = OpenDevice(VID, PID); if (hid_ret == HID_RETURN.SUCCESS) { Console.WriteLine("打開設備成功"); _openDeviceFlag = (int)HID_RETURN.SUCCESS; return true; } else { _openDeviceFlag = (int)hid_ret; return false; } } catch (System.Exception ex) { return false; } } /// <summary> /// Helper to get the HID guid. /// </summary> public static Guid HIDGuid { get { Guid gHid = Guid.Empty; HidD_GetHidGuid(ref gHid); return gHid; //return new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); //gHid; } }