创建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;
}
///
/// Provides details about a single USB device
///
[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;
}
///
/// 打开指定信息的设备
///
/// 设备的vID
/// 设备的pID
/// 设备的serial,string serial
///
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 deviceList = new List();
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;
}
///
/// 关闭打开的设备
///
public void CloseDevice()
{
if (deviceOpened == true)
{
hidDevice.Close();
deviceOpened = false;
}
}
///
/// 开始一次异步读
///
private void BeginAsyncRead()
{
byte[] inputBuff = new byte[InputReportLength];
hidDevice.BeginRead(inputBuff, 0, InputReportLength, new AsyncCallback(ReadCompleted), inputBuff);
}
///
/// 异步读取结束,发出有数据到达事件
///
/// 这里是输入报告的数组
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();
}
}
///
/// 事件:数据到达,处理此事件以接收输入数据
///
public event EventHandler DataReceived;
protected virtual void OnDataReceived(Report e)
{
if(DataReceived != null) DataReceived(this, e);
}
///
/// 事件:设置连接
///
public event EventHandler DeviceArrived;
protected virtual void OnDeviceArrived( EventArgs e )
{
if (DeviceArrived != null) DeviceArrived(this, e);
}
///
/// 事件:设备断开
///
public event EventHandler DeviceRemoved;
protected virtual void OnDeviceRemoved(EventArgs e)
{
if (DeviceRemoved != null) DeviceRemoved(this, e);
}
///
/// 发送report命令
///
///
///
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);
}
///
/// 发送字节数组命令
///
///
///
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
///
/// closes open handles to a device.
///
///
/// the handle for learning about the device and exchanging Feature reports.
/// the handle for reading Input reports from the device.
/// the handle for writing Output reports to the device.
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;
}
}
///
/// Creates an event object for the overlapped structure used with
/// ReadFile. Called before the first call to ReadFile.
///
///
/// the overlapped structure
/// the event object
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;
}
}
///
/// reads an Input report from the device using interrupt transfers.
///
///
/// the handle for learning about the device and exchanging Feature reports.
/// the handle for reading Input reports from the device.
/// the handle for writing Output reports to the device.
/// tells whether the device is currently attached.
/// contains the requested report.
/// read success
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
{
///
/// 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.
///
internal abstract void Read(SafeFileHandle readHandle, ref Boolean myDeviceDetected, ref Byte[] readBuffer, ref Boolean success);
}
///
/// 获取所有连接的hid的设备路径
///
/// 包含每个设备路径的字符串数组
public static void GetHidDeviceList(ref List 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;
}
}
///
/// Helper to get the HID guid.
///
public static Guid HIDGuid
{
get
{
Guid gHid = Guid.Empty;
HidD_GetHidGuid(ref gHid);
return gHid;
//return new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); //gHid;
}
}