C#和USB HID进行通讯,实现发送、接收数据主要是通过两个函数实现的
FileStream.Write(…)
(发送数据)
、
FileStream.Read(…)
(接收数据)
。
Write和Read是同步,BeginWrite和BeginRead是异步。
或者是c++的库函数WriteFile()、ReadFile(),在库kernel32.dll中。(但是我的项目发现WriteFile()、ReadFile()不能通讯)
1、准备工作
c#中一定要先申明c++库操作HID设备的函数:
[DllImport(“hid.dll”)]
private static extern void HidD_GetHidGuid(ref Guid HidGuid);
[DllImport(“setupapi.dll”, SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, USBHIDEnum.DIGCF Flags);
[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);
public struct SP_DEVICE_INTERFACE_DATA
{
public int cbSize;
public Guid interfaceClassGuid;
public int flags;
public int reserved;
}
[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);
[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;
}
[DllImport(“setupapi.dll”, CharSet = CharSet.Auto, SetLastError = true)]
private static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet);
[DllImport(“kernel32.dll”, SetLastError = true)]
static extern IntPtr CreateFile(
string FileName, // 文件名
uint DesiredAccess, // 访问模式
uint ShareMode, // 共享模式
uint SecurityAttributes, // 安全属性
uint CreationDisposition, // 如何创建
uint FlagsAndAttributes, // 文件属性
int hTemplateFile // 模板文件的句柄
);
[DllImport(“kernel32.dll”, SetLastError = true)]
public static extern Boolean WriteFile(
IntPtr hFile,
byte[] lpBuffer,
uint nNumberOfBytesToWrite,
ref uint nNumberOfBytesWrite,
IntPtr lpOverlapped
);
[DllImport(“kernel32.dll”, SetLastError = true)]
public static extern Boolean ReadFile(
IntPtr hFile,
byte[] lpBuffer,
uint nNumberOfBytesToRead,
ref uint nNumberOfBytesRead,
IntPtr lpOverlapped
);
[DllImport(“hid.dll”)]
private static extern Boolean HidD_GetAttributes(IntPtr hidDeviceObject, out HIDD_ATTRIBUTES attributes);
[DllImport(“hid.dll”)]
private static extern Boolean HidD_GetPreparsedData(IntPtr hidDeviceObject, out IntPtr PreparsedData);
[DllImport(“hid.dll”)]
private static extern uint HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities);
[DllImport(“hid.dll”)]
private static extern Boolean HidD_FreePreparsedData(IntPtr PreparsedData);
[DllImport(“kernel32.dll”, SetLastError = true)]
private static extern int CloseHandle(int hObject);
以上这些函数申明网上有别人封装好的类,可以自己找一找。我用的把这些函数套了一层,类似下面这样,(在同一个类中)
internal void GetDeviceGuid(ref Guid HIDGuid)
{
HidD_GetHidGuid(ref HIDGuid);
}
internal IntPtr GetClassDevOfHandle(Guid HIDGuid)
{
return SetupDiGetClassDevs(ref HIDGuid,0,IntPtr.Zero,USBHIDEnum.DIGCF.DIGCF_PRESENT|USBHIDEnum.DIGCF.DIGCF_DEVICEINTERFACE);
}
注意,c++有有些类型c#没有,要自己转换一下。
2、获取HID设备的名称
List<string> deviceList = new List<string>();
//保存HID设备的名称。开始是列出所有的HID设备,后来要求只连接固定的VID和PID
private void GetDeviceList(ref List<string> deviceList, string vid, string pid)
{
Guid HIDGuid = Guid.Empty;
windowsApi.GetDeviceGuid(ref HIDGuid);
//获取HID的全局GUID
IntPtr HIDInfoSet =
windowsApi
.GetClassDevOfHandle(HIDGuid);
//获取包含所有HID接口信息集合的句柄
//这里的windowsApi是我声明hid操作和封装这些函数的类
if (HIDInfoSet != IntPtr.Zero)
{
SP_DEVICE_INTERFACE_DATA interfaceInfo = new SP_DEVICE_INTERFACE_DATA();
interfaceInfo.cbSize = Marshal.SizeOf(interfaceInfo);
//检测集合的每个接口
for (uint index = 0; index < MAX_USB_DEVICES; index++)
{
//获取接口信息
if (!windowsApi.GetEnumDeviceInterfaces(HIDInfoSet, ref HIDGuid, index, ref interfaceInfo))
continue;
int buffsize = 0;
//获取接口详细信息;第一次读取错误,但可取得信息缓冲区的大小
windowsApi.GetDeviceInterfaceDetail(HIDInfoSet, ref interfaceInfo, IntPtr.Zero, ref buffsize);
//接受缓冲
IntPtr pDetail = Marshal.AllocHGlobal(buffsize);
SP_DEVICE_INTERFACE_DETAIL_DATA detail = new WindowsAPI.SP_DEVICE_INTERFACE_DETAIL_DATA();
detail.cbSize = Marshal.SizeOf(typeof(USBHIDControl.WindowsAPI.SP_DEVICE_INTERFACE_DETAIL_DATA));
Marshal.StructureToPtr(detail, pDetail, false);
if (windowsApi.GetDeviceInterfaceDetail(HIDInfoSet, ref interfaceInfo, pDetail, ref buffsize))
//第二次读取接口详细信息
{
string deviceVIDPID = “vid_” + vid + “&pid_” + pid;
string str = Marshal.PtrToStringAuto((IntPtr)((int)pDetail + 4));
//如果要获取所有的设备(包括HID以外的设备),去掉下面的if语句,直接deviceList.Add(str);
if (str.IndexOf(deviceVIDPID) >= 0)
{
deviceList.Add(str);
//获取带有固定vid或pid的设备字符串句柄
}
}
Marshal.FreeHGlobal(pDetail);
}
}
//删除设备信息并释放内存
windowsApi.DestroyDeviceInfoList(HIDInfoSet);
}
3、打开HID设备
private int outputReportLength;
private int inputReportLength;
private FileStream hidDevice;
private IntPtr device;
private const int MAX_USB_DEVICES = 64;
public bool OpenUSBHid(string deviceStr)
{
//创建,打开设备文件
device = windowsApi.CreateDeviceFile(deviceStr);
if (device == new IntPtr(-1))
return false;
HIDD_ATTRIBUTES attributes;
windowsApi.GETDeviceAttribute(device, out attributes);
//找到相对应的HID设备信息
IntPtr preparseData;
HIDP_CAPS caps;
windowsApi.GetPreparseData(device, out preparseData);
windowsApi.GetCaps(preparseData, out caps);
windowsApi.FreePreparseData(preparseData);
outputReportLength = caps.OutputReportByteLength;
inputReportLength = caps.InputReportByteLength;
hidDevice = new FileStream(new SafeFileHandle(device, false), FileAccess.ReadWrite, inputReportLength, true);
return true;
}
4、以上操作都没问题就可以发送数据给HID设备了
(有的是只有先发送数据才有回传数据;有的不用发送直接有回传数据的,可以跳过此步,直接读取数据)
internal string WriteHID(string sendValue)
{
try
{
byte[] array = System.Text.ASCIIEncoding.Default.GetBytes(sendValue);
byte[] arrays=new byte[array.Length+1];
arrays[0] = 0;
for (int i = 1; i <= array.Length; i++)
{
arrays[i]=array[i-1];
}
hidDevice.Write(arrays, 0, arrays.Length);
//uint size = 0;
//windowsApi.WriteDeviceFile(device, arrays, (uint)inputReportLength, ref size, IntPtr.Zero);
//注释掉的是调用的c++的库函数bool WriteFile(hFile, lpBuffer, nNumberOfBytesToRead, ref nNumberOfBytesRead, lpOverlapped),发现返回值一直是0,就是没有发送成功。所以用的FileStream.Write()函数
return sendValue;
}
catch (Exception e)
{
return e.Message;
}
}
5、读取HID设备回传回来的数据
public void BeginAsyncRead(string sendValue)
{
byte[] inputBuff = new byte[inputReportLength];
IAsyncResult asyncResult = hidDevice.BeginRead(inputBuff, 0, inputReportLength, new AsyncCallback(ReadCompleted), inputBuff);
if (asyncResult.IsCompleted == false)
//这里处理数据发送成功,但是没有到达设备HID,具体怎么处理根据自己情况定
{
WriteHID(sendValue);
…;
//这里一定不能调用BeginAsyncRead(sendValue),否则会死循环
}
}
private void ReadCompleted(IAsyncResult iResult)
//异步读取成功的回调函数
{
byte[] readBuff = (byte[])(iResult.AsyncState);
try
{
hidDevice.
EndRead
(iResult);
//读取结束,如果读取错误就会产生一个异常
(EndRead和BeginRead要一起使用)
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);
//发出数据到达消息,把e传回到窗体界面显示
}
catch (IOException)
//读写错误,设备已经被移除
{
EventArgs ex = new EventArgs();
OnDeviceRemoved(ex);
//发出设备移除消息
CloseDevice();
}
}
事件的定义如下:
public event EventHandler DataReceived;
protected virtual void OnDataReceived(EventArgs e)
{
if (DataReceived != null) DataReceived(this, e);
}
public event EventHandler DeviceRemoved;
protected virtual void OnDeviceRemoved(EventArgs e)
{
if (DeviceRemoved != null) DeviceRemoved(this, e);
}
说明,
一般发送数据用同步,接收数据一定要异步。因为发送数据要么成功要么失败,都是即时给出结果信息。但是接收就不一样,直接用Read会出现死机。
因为调试发现会有这种情况,数据发送成功,但是没有到达设备HID;或者发送成功,HID有返回数据,但是Read没有读到。这种情况Read不会抛出异常,什么反应都没有,程序直接死掉;或者界面根本没出来直接在后台死机。
6、OK,试一下能不能通讯成功
窗体的后台程序要绑定上面的事件
usbHID.DataReceived += usbHID_DataReceived;
usbHID.DeviceRemoved += usbHID_DeviceRemoved;
void usbHID_DeviceRemoved(object sender, EventArgs e)
{
if (InvokeRequired)
{
Invoke(new EventHandler(usbHID_DeviceRemoved), new object[] { sender, e });
}
else
{
tb_information.Text = “设备连接”;
}
}
void usbHID_DataReceived(object sender, EventArgs e)
{
report myRP = (report)e;
if (InvokeRequired)
{
Invoke(new EventHandler(usbHID_DataReceived), new object[] { sender, e });
}
else
{。。。。;
//自己的处理
}
}
7、异常说明
如果通信不成功,特别是发送不成功,看看传的参数即字符串对不对
发送的参数及发送给HID的report有一定的格式说明,前几位有特别的意义,注意查看一下。第一位貌似是ID,但是我不清楚这个ID是标识什么的。我开始发送不成功,然后在发送的字符串前面即第一位加了一个byte转成的char,byte值为0。然后就可以发送成功了。
下来我会研究一下HID的report的说明。
此外网上有关C#通讯USB HID的资料也不是很多,大体都是这样的。所以如果有作者发现雷同部分请多多谅解
-_-