работа с usb-устройствами в .net

<p>используя .net (c #), как вы можете работать с usb-устройствами? </p><p>как вы можете обнаружить события usb (подключения/отключения) и как вы общаетесь с устройствами (чтение/запись).</p><p>есть ли для этого решение .net?</p>

<p>для этого не существует <em>native</em> (например, системных библиотек). вот почему <a href="http://www.icsharpcode.net/opensource/sharpusblib/">sharpusblib</a> существует, как упомянуто <a href="https://stackoverflow.com/questions/68749/working-with-usb-devices-in-net#68777">moobaa</a> .</p><p>если вы хотите свернуть свой собственный обработчик для usb-устройств, вы можете проверить <a href="https://docs.microsoft.com/en-us/dotnet/api/system.io.ports.serialport">класс serialport system.io.ports</a> .</p>

<p>я попытался использовать sharpusblib, и он испортил мой компьютер (требуется восстановление системы). так случилось и с коллегой по тому же проекту.</p><p>я нашел альтернативу в libusbdotnet: <a href="http://sourceforge.net/projects/libusbdotnet">http://sourceforge.net/projects/libusbdotnet</a> пока еще не использовал его, но, кажется, хорошо и недавно обновил (в отличие от sharp).</p><p>правка: по состоянию на середину февраля 2017 года, libusbdotnet был обновлен около 2 недель назад. между тем sharpusblib не обновлялся с 2004 года.</p>

<p>я использовал следующий код, чтобы определить, когда usb-устройства были подключены и отключены от моего компьютера:</p><pre><code>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(); } } </code></pre><p>вы должны убедиться, что вызываете метод dispose () при закрытии приложения. в противном случае вы получите ошибку com-объекта во время выполнения при закрытии.</p>

<p> <a href="http://www.icsharpcode.net/opensource/sharpusblib/">usb-библиотека #usblib для .net</a> </p>

<p>я бы порекомендовал <a href="http://sourceforge.net/projects/libusbdotnet/">libusbdotnet</a> , библиотеку, которой я пользуюсь уже 2 года . если вам нужно работать с usb-устройством (отправлять запросы, обрабатывать ответы), эта библиотека была лучшим решением, которое я мог найти.</p><p><strong>плюсы:</strong></p><ul> <li>имеет все методы, необходимые для работы в режиме синхронизации или асинхронности.</li> <li>исходный код предоставлен</li> <li>достаточно образцов, чтобы начать использовать его сразу.</li> </ul><p><strong>минусы:</strong></p><ul> <li>плохая документация (это общая проблема для проектов с открытым исходным кодом). по сути, вы можете найти просто общее описание методов в файле справки chm и все. но я все еще нахожу предоставленные примеры, и исходного кода достаточно для кодирования . просто иногда я вижу странное поведение и хочу знать, почему оно было реализовано таким образом, и не могу получить даже подсказки ...</li> <li>кажется, больше не поддерживается. последняя версия была выпущена в октябре 2010 года. и иногда трудно получить ответы.</li> </ul>

<p>здесь приведено руководство по настройке библиотеки sharpusblib и драйверов hid для работы с c #: </p><p> <a href="http://www.developerfusion.com/article/84338/making-usb-c-friendly/">http://www.developerfusion.com/article/84338/making-usb-c-friendly/</a> </p>

<p>если на вашем пк установлено программное обеспечение <strong>national instruments</strong>, вы можете создать драйвер usb с помощью их <em>«мастер драйверов ni-visa»</em>. </p><p>шаги по созданию драйвера usb: <a href="http://www.ni.com/tutorial/4478/en/">http://www.ni.com/tutorial/4478/en/</a> </p><p>создав драйвер, вы сможете записывать и считывать байты на любое usb-устройство.</p><p>убедитесь, что драйвер виден окнами в диспетчере устройств:</p><p><img src="https://i.stack.imgur.com/zjbla.png" alt="enter image description here"></p><p>код c #:</p><pre><code> 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 </code></pre><p>использование:</p><pre><code>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(); </code></pre><p>надеюсь, это кому-нибудь поможет.</p>

<p>usb-устройства обычно подразделяются на две категории: hid и usb. устройство usb может быть, а может и не быть устройством hid, и наоборот. спрятать обычно немного проще, чем прямой usb. различные платформы имеют разные api для работы с usb и hid. </p><p>вот документация для uwp:</p><p>usb: <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/how-to-connect-to-a-usb-device--uwp-app-">https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/how-to-connect-to-a-usb-device--uwp-app-</a> </p><p>спрятано: <a href="https://docs.microsoft.com/en-us/uwp/api/windows.devices.humaninterfacedevice">https://docs.microsoft.com/en-us/uwp/api/windows.devices.humaninterfacedevice</a> </p><p>вот документация для android: <a href="https://developer.xamarin.com/api/namespace/android.hardware.usb/">https://developer.xamarin.com/api/namespace/android.hardware.usb/</a> </p><p>вот два класса для работы с usb/hid на уровне api windows:</p><p> <a href="https://github.com/melbournedeveloper/device.net/blob/master/src/hid.net/windows/hidapicalls.cs">https://github.com/melbournedeveloper/device.net/blob/master/src/hid.net/windows/hidapicalls.cs</a> </p><pre><code>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 </code></pre><p>}</p><p> <a href="https://github.com/melbournedeveloper/device.net/blob/master/src/usb.net/windows/winusbapicalls.cs">https://github.com/melbournedeveloper/device.net/blob/master/src/usb.net/windows/winusbapicalls.cs</a> </p><pre><code>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 } </code></pre><p>в любом из этих решений вам нужно будет либо опрашивать устройство с определенным интервалом, либо использовать один из классов прослушивания нативного устройства api. тем не менее, эта библиотека размещает слой через hid и usb на всех платформах, чтобы вы могли легко обнаруживать соединения и разъединения: <a href="https://github.com/melbournedeveloper/device.net/wiki/device-listener">https://github.com/melbournedeveloper/device.net/wiki/device-listener</a> . вот как вы бы это использовали:</p><pre><code>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 } </code></pre>

<p>существует универсальный инструментарий <a href="http://www.jungo.com/st/windriver\_usb\_pci\_driver\_development\_software.html#">windriver</a> для написания usb-драйверов в пользовательском режиме, который также поддерживает # .net </p>

<p>я попробовал несколько из этих предложений без удачи. в итоге я написал рабочее решение с использованием java и библиотеки <a href="https://github.com/gary-rowe/hid4java">hid4java</a> . в качестве консольного приложения я могу использовать shell из c # с помощью <code>process.start()</code>, передавая параметры и читая ответы. это обеспечивает базовый ввод-вывод hid, но без событий подключения/отключения. для этого мне нужно переписать его, чтобы он работал как демон/служба и использовал именованные каналы или какой-либо другой сервер/клиентский транспорт. на данный момент достаточно выполнить работу, поскольку библиотека hi4java «просто работает».</p>

<p>я получил интерфейс для <a href="http://www.pjrc.com/teensy/index.html">teensy</a> работает довольно хорошо, используя <a href="http://www.codeproject.com/articles/18099/a-usb-hid-component-for-c">эта статья</a> </p>

<p>большинство чипсетов usb поставляются с драйверами. <a href="https://www.silabs.com/products/mcu/usb/pages/default.aspx">silicon labs</a> есть один.</p>