Working with USB devices in .NET
使用.Net(C#),如何使用USB设备?
如何检测USB事件(连接/断开连接)以及如何与设备通信(读/写)。
是否存在本机.Net解决方案?
我曾尝试使用SharpUSBLib,但它弄坏了我的计算机(需要系统还原)。也发生在同一项目的同事中。
我在LibUSBDotNet中找到了替代方法:http://sourceforge.net/projects/libusbdotnet
哈夫(Hav)并没有使用太多,但看起来不错,并且最近进行了更新(与夏普不同)。
编辑:截至2017年2月中,LibUSBDotNet大约在2周前进行了更新。同时,SharpUSBLib自2004年以来未进行更新。
没有针对此的本地(例如系统库)解决方案。这就是moobaa提到存在SharpUSBLib的原因。
如果您希望使用自己的USB设备处理程序,则可以检出System.IO.Ports的SerialPort类。
我使用以下代码来检测何时从计算机上插入和拔出USB设备:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| class USBControl : IDisposable
{
// used for monitoring plugging and unplugging of USB devices.
private ManagementEventWatcher watcherAttach;
private ManagementEventWatcher watcherRemove;
public USBControl()
{
// Add USB plugged event watching
watcherAttach = new ManagementEventWatcher();
//var queryAttach = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
watcherAttach.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
watcherAttach.Query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
watcherAttach.Start();
// Add USB unplugged event watching
watcherRemove = new ManagementEventWatcher();
//var queryRemove = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
watcherRemove.EventArrived += new EventArrivedEventHandler(watcher_EventRemoved);
watcherRemove.Query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
watcherRemove.Start();
}
/// <summary>
/// Used to dispose of the USB device watchers when the USBControl class is disposed of.
/// </summary>
public void Dispose()
{
watcherAttach.Stop();
watcherRemove.Stop();
//Thread.Sleep(1000);
watcherAttach.Dispose();
watcherRemove.Dispose();
//Thread.Sleep(1000);
}
void watcher_EventArrived(object sender, EventArrivedEventArgs e)
{
Debug.WriteLine("watcher_EventArrived");
}
void watcher_EventRemoved(object sender, EventArrivedEventArgs e)
{
Debug.WriteLine("watcher_EventRemoved");
}
~USBControl()
{
this.Dispose();
}
} |
您必须确保在关闭应用程序时调用Dispose()方法。否则,关闭时将在运行时收到COM对象错误。
我建议我使用两年的LibUSBDotNet。
如果必须使用USB设备(发送请求,处理响应),则该库是我能找到的最佳解决方案。
优点:
-
具有在同步或异步模式下工作所需的所有方法。
-
提供的源代码
-
足够的样本可以立即开始使用。
缺点:
-
文档不佳(这是开源项目的常见问题)。基本上,您可以在CHM帮助文件中找到方法的通用说明,仅此而已。
但是我仍然发现提供的示例和源代码足以进行编码。
有时候我看到一种奇怪的行为,想知道为什么以这种方式实现它,甚至无法获得提示。
-
似乎不再受支持。最新版本于2010年10月发布。有时很难获得答案。
USB设备通常分为两类:隐藏和USB。 USB设备可能是也可能不是Hid设备,反之亦然。与直接USB相比,隐藏通常更易于使用。不同的平台具有用于处理USB和Hid的不同API。
以下是UWP的文档:
USB:
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/usbcon/how-to-connect-to-a-usb-device--uwp-app-
隐藏:
https://docs.microsoft.com/zh-cn/uwp/api/windows.devices.humaninterfacedevice
以下是Android的文档:
https://developer.xamarin.com/api/namespace/Android.Hardware.Usb/
这里有两个用于在原始Windows API级别处理USB / Hid的类:
https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Hid.Net/Windows/HidAPICalls.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| public static class HidAPICalls
{
#region Constants
private const int DigcfDeviceinterface = 16;
private const int DigcfPresent = 2;
private const uint FileShareRead = 1;
private const uint FileShareWrite = 2;
private const uint GenericRead = 2147483648;
private const uint GenericWrite = 1073741824;
private const uint OpenExisting = 3;
private const int HIDP_STATUS_SUCCESS = 0x110000;
private const int HIDP_STATUS_INVALID_PREPARSED_DATA = -0x3FEF0000;
#endregion
#region API Calls
[DllImport("hid.dll", SetLastError = true)]
private static extern bool HidD_GetPreparsedData(SafeFileHandle hidDeviceObject, out IntPtr pointerToPreparsedData);
[DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool HidD_GetManufacturerString(SafeFileHandle hidDeviceObject, IntPtr pointerToBuffer, uint bufferLength);
[DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool HidD_GetProductString(SafeFileHandle hidDeviceObject, IntPtr pointerToBuffer, uint bufferLength);
[DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool HidD_GetSerialNumberString(SafeFileHandle hidDeviceObject, IntPtr pointerToBuffer, uint bufferLength);
[DllImport("hid.dll", SetLastError = true)]
private static extern int HidP_GetCaps(IntPtr pointerToPreparsedData, out HidCollectionCapabilities hidCollectionCapabilities);
[DllImport("hid.dll", SetLastError = true)]
private static extern bool HidD_GetAttributes(SafeFileHandle hidDeviceObject, out HidAttributes attributes);
[DllImport("hid.dll", SetLastError = true)]
private static extern bool HidD_FreePreparsedData(ref IntPtr pointerToPreparsedData);
[DllImport("hid.dll", SetLastError = true)]
private static extern void HidD_GetHidGuid(ref Guid hidGuid);
private delegate bool GetString(SafeFileHandle hidDeviceObject, IntPtr pointerToBuffer, uint bufferLength);
#endregion
#region Helper Methods
#region Public Methods
public static HidAttributes GetHidAttributes(SafeFileHandle safeFileHandle)
{
var isSuccess = HidD_GetAttributes(safeFileHandle, out var hidAttributes);
WindowsDeviceBase.HandleError(isSuccess,"Could not get Hid Attributes");
return hidAttributes;
}
public static HidCollectionCapabilities GetHidCapabilities(SafeFileHandle readSafeFileHandle)
{
var isSuccess = HidD_GetPreparsedData(readSafeFileHandle, out var pointerToPreParsedData);
WindowsDeviceBase.HandleError(isSuccess,"Could not get pre parsed data");
var result = HidP_GetCaps(pointerToPreParsedData, out var hidCollectionCapabilities);
if (result != HIDP_STATUS_SUCCESS)
{
throw new Exception($"Could not get Hid capabilities. Return code: {result}");
}
isSuccess = HidD_FreePreparsedData(ref pointerToPreParsedData);
WindowsDeviceBase.HandleError(isSuccess,"Could not release handle for getting Hid capabilities");
return hidCollectionCapabilities;
}
public static string GetManufacturer(SafeFileHandle safeFileHandle)
{
return GetHidString(safeFileHandle, HidD_GetManufacturerString);
}
public static string GetProduct(SafeFileHandle safeFileHandle)
{
return GetHidString(safeFileHandle, HidD_GetProductString);
}
public static string GetSerialNumber(SafeFileHandle safeFileHandle)
{
return GetHidString(safeFileHandle, HidD_GetSerialNumberString);
}
#endregion
#region Private Static Methods
private static string GetHidString(SafeFileHandle safeFileHandle, GetString getString)
{
var pointerToBuffer = Marshal.AllocHGlobal(126);
var isSuccess = getString(safeFileHandle, pointerToBuffer, 126);
Marshal.FreeHGlobal(pointerToBuffer);
WindowsDeviceBase.HandleError(isSuccess,"Could not get Hid string");
return Marshal.PtrToStringUni(pointerToBuffer);
}
#endregion
#endregion |
} ??
https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Usb.Net/Windows/WinUsbApiCalls.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| public static partial class WinUsbApiCalls
{
#region Constants
public const int EnglishLanguageID = 1033;
public const uint DEVICE_SPEED = 1;
public const byte USB_ENDPOINT_DIRECTION_MASK = 0X80;
public const int WritePipeId = 0x80;
/// <summary>
/// Not sure where this constant is defined...
/// </summary>
public const int DEFAULT_DESCRIPTOR_TYPE = 0x01;
public const int USB_STRING_DESCRIPTOR_TYPE = 0x03;
#endregion
#region API Calls
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_ControlTransfer(IntPtr InterfaceHandle, WINUSB_SETUP_PACKET SetupPacket, byte[] Buffer, uint BufferLength, ref uint LengthTransferred, IntPtr Overlapped);
[DllImport("winusb.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool WinUsb_GetAssociatedInterface(SafeFileHandle InterfaceHandle, byte AssociatedInterfaceIndex, out SafeFileHandle AssociatedInterfaceHandle);
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_GetDescriptor(SafeFileHandle InterfaceHandle, byte DescriptorType, byte Index, ushort LanguageID, out USB_DEVICE_DESCRIPTOR deviceDesc, uint BufferLength, out uint LengthTransfered);
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_GetDescriptor(SafeFileHandle InterfaceHandle, byte DescriptorType, byte Index, UInt16 LanguageID, byte[] Buffer, UInt32 BufferLength, out UInt32 LengthTransfered);
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_Free(SafeFileHandle InterfaceHandle);
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_Initialize(SafeFileHandle DeviceHandle, out SafeFileHandle InterfaceHandle);
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_QueryDeviceInformation(IntPtr InterfaceHandle, uint InformationType, ref uint BufferLength, ref byte Buffer);
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_QueryInterfaceSettings(SafeFileHandle InterfaceHandle, byte AlternateInterfaceNumber, out USB_INTERFACE_DESCRIPTOR UsbAltInterfaceDescriptor);
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_QueryPipe(SafeFileHandle InterfaceHandle, byte AlternateInterfaceNumber, byte PipeIndex, out WINUSB_PIPE_INFORMATION PipeInformation);
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_ReadPipe(SafeFileHandle InterfaceHandle, byte PipeID, byte[] Buffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_SetPipePolicy(IntPtr InterfaceHandle, byte PipeID, uint PolicyType, uint ValueLength, ref uint Value);
[DllImport("winusb.dll", SetLastError = true)]
public static extern bool WinUsb_WritePipe(SafeFileHandle InterfaceHandle, byte PipeID, byte[] Buffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);
#endregion
#region Public Methods
public static string GetDescriptor(SafeFileHandle defaultInterfaceHandle, byte index, string errorMessage)
{
var buffer = new byte[256];
var isSuccess = WinUsb_GetDescriptor(defaultInterfaceHandle, USB_STRING_DESCRIPTOR_TYPE, index, EnglishLanguageID, buffer, (uint)buffer.Length, out var transfered);
WindowsDeviceBase.HandleError(isSuccess, errorMessage);
var descriptor = new string(Encoding.Unicode.GetChars(buffer, 2, (int)transfered));
return descriptor.Substring(0, descriptor.Length - 1);
}
#endregion
} |
使用这些解决方案中的任何一个,您要么需要按一定的时间间隔轮询设备,要么使用API??的本机设备侦听类之一。但是,该库在所有平台上的Hid和USB上都放置了一层,以便您可以轻松检测连接和断开连接:https://github.com/MelbourneDeveloper/Device.Net/wiki/Device-Listener。这是您将如何使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| internal class TrezorExample : IDisposable
{
#region Fields
//Define the types of devices to search for. This particular device can be connected to via USB, or Hid
private readonly List<FilterDeviceDefinition> _DeviceDefinitions = new List<FilterDeviceDefinition>
{
new FilterDeviceDefinition{ DeviceType= DeviceType.Hid, VendorId= 0x534C, ProductId=0x0001, Label="Trezor One Firmware 1.6.x", UsagePage=65280 },
new FilterDeviceDefinition{ DeviceType= DeviceType.Usb, VendorId= 0x534C, ProductId=0x0001, Label="Trezor One Firmware 1.6.x (Android Only)" },
new FilterDeviceDefinition{ DeviceType= DeviceType.Usb, VendorId= 0x1209, ProductId=0x53C1, Label="Trezor One Firmware 1.7.x" },
new FilterDeviceDefinition{ DeviceType= DeviceType.Usb, VendorId= 0x1209, ProductId=0x53C0, Label="Model T" }
};
#endregion
#region Events
public event EventHandler TrezorInitialized;
public event EventHandler TrezorDisconnected;
#endregion
#region Public Properties
public IDevice TrezorDevice { get; private set; }
public DeviceListener DeviceListener { get; private set; }
#endregion
#region Event Handlers
private void DevicePoller_DeviceInitialized(object sender, DeviceEventArgs e)
{
TrezorDevice = e.Device;
TrezorInitialized?.Invoke(this, new EventArgs());
}
private void DevicePoller_DeviceDisconnected(object sender, DeviceEventArgs e)
{
TrezorDevice = null;
TrezorDisconnected?.Invoke(this, new EventArgs());
}
#endregion
#region Public Methods
public void StartListening()
{
TrezorDevice?.Dispose();
DeviceListener = new DeviceListener(_DeviceDefinitions, 3000);
DeviceListener.DeviceDisconnected += DevicePoller_DeviceDisconnected;
DeviceListener.DeviceInitialized += DevicePoller_DeviceInitialized;
}
public async Task InitializeTrezorAsync()
{
//Get the first available device and connect to it
var devices = await DeviceManager.Current.GetDevices(_DeviceDefinitions);
TrezorDevice = devices.FirstOrDefault();
await TrezorDevice.InitializeAsync();
}
public async Task<byte[]> WriteAndReadFromDeviceAsync()
{
//Create a buffer with 3 bytes (initialize)
var writeBuffer = new byte[64];
writeBuffer[0] = 0x3f;
writeBuffer[1] = 0x23;
writeBuffer[2] = 0x23;
//Write the data to the device
return await TrezorDevice.WriteAndReadAsync(writeBuffer);
}
public void Dispose()
{
TrezorDevice?.Dispose();
}
#endregion
} |
这里有一个有关如何使SharpUSBLib库和HID驱动程序与C#一起使用的教程:
http://www.developerfusion.com/article/84338/making-usb-c-friendly/
如果您的PC上装有National Instruments软件,则可以使用其" NI-VISA驱动程序向导"创建USB驱动程序。
创建USB驱动程序的步骤:http://www.ni.com/tutorial/4478/en/
创建驱动程序后,您将可以向任何USB设备写入和读取字节。
确保在设备管理器下的Windows中可以看到驱动程序:
C#代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| using NationalInstruments.VisaNS;
#region UsbRaw
/// <summary>
/// Class to communicate with USB Devices using the UsbRaw Class of National Instruments
/// </summary>
public class UsbRaw
{
private NationalInstruments.VisaNS.UsbRaw usbRaw;
private List<byte> DataReceived = new List<byte>();
/// <summary>
/// Initialize the USB Device to interact with
/// </summary>
/// <param name="ResourseName">In this format:"USB0::0x1448::0x8CA0::NI-VISA-30004::RAW". Use the NI-VISA Driver Wizard from Start??All Programs??National Instruments??VISA??Driver Wizard to create the USB Driver for the device you need to talk to.</param>
public UsbRaw(string ResourseName)
{
usbRaw = new NationalInstruments.VisaNS.UsbRaw(ResourseName, AccessModes.NoLock, 10000, false);
usbRaw.UsbInterrupt += new UsbRawInterruptEventHandler(OnUSBInterrupt);
usbRaw.EnableEvent(UsbRawEventType.UsbInterrupt, EventMechanism.Handler);
}
/// <summary>
/// Clears a USB Device from any previous commands
/// </summary>
public void Clear()
{
usbRaw.Clear();
}
/// <summary>
/// Writes Bytes to the USB Device
/// </summary>
/// <param name="EndPoint">USB Bulk Out Pipe attribute to send the data to. For example: If you see on the Bus Hound sniffer tool that data is coming out from something like 28.4 (Device column), this means that the USB is using Endpoint 4 (Number after the dot)</param>
/// <param name="BytesToSend">Data to send to the USB device</param>
public void Write(short EndPoint, byte[] BytesToSend)
{
usbRaw.BulkOutPipe = EndPoint;
usbRaw.Write(BytesToSend); // Write to USB
}
/// <summary>
/// Reads bytes from a USB Device
/// </summary>
/// <returns>Bytes Read</returns>
public byte[] Read()
{
usbRaw.ReadByteArray(); // This fires the UsbRawInterruptEventHandler
byte[] rxBytes = DataReceived.ToArray(); // Collects the data received
return rxBytes;
}
/// <summary>
/// This is used to get the data received by the USB device
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnUSBInterrupt(object sender, UsbRawInterruptEventArgs e)
{
try
{
DataReceived.Clear(); // Clear previous data received
DataReceived.AddRange(e.DataBuffer);
}
catch (Exception exp)
{
string errorMsg ="Error:" + exp.Message;
DataReceived.AddRange(ASCIIEncoding.ASCII.GetBytes(errorMsg));
}
}
/// <summary>
/// Use this function to clean up the UsbRaw class
/// </summary>
public void Dispose()
{
usbRaw.DisableEvent(UsbRawEventType.UsbInterrupt, EventMechanism.Handler);
if (usbRaw != null)
{
usbRaw.Dispose();
}
}
}
#endregion UsbRaw |
用法:
1 2 3 4 5 6 7
| UsbRaw usbRaw = new UsbRaw("USB0::0x1448::0x8CA0::NI-VISA-30004::RAW");
byte[] sendData = new byte[] { 0x53, 0x4c, 0x56 };
usbRaw.Write(4, sendData); // Write bytes to the USB Device
byte[] readData = usbRaw.Read(); // Read bytes from the USB Device
usbRaw.Dispose(); |
希望这对某人有帮助。
有一个通用工具包WinDriver,用于在用户模式下编写支持#.NET的USB驱动程序。
我很幸运地尝试了其中一些建议。我最终使用Java和hid4java库编写了一个可行的解决方案。作为控制台应用程序,我可以使用Process.Start()从C#中将其外壳化,并传递参数以及读取响应。这提供了基本的HID I / O,但没有连接/断开事件。为此,我需要将其重写以作为守护程序/服务运行,并使用命名管道或其他服务器/客户端传输。到目前为止,自hi4java库"正常运行"以来,足以完成工作。
使用本文,我已经获得了Teensy的界面,效果很好
大多数USB芯片组都附带驱动程序。 Silicon Labs有一个。
|