Windows API USB IO (winusb.dll)
Изменить: этот вопрос развивался с течением времени. Основной вопрос был о том, как подключиться к устройству USB и читать / записывать на него / с него в Windows. В конце концов я ответил на вопрос с помощью @benvoigt.
Я написал библиотеку Hid, которая пишет и читает с / на USB-устройства Hid. Это работает хорошо. Однако устройство, к которому я подключаюсь, переключилось с Hid на USB-порт. Код Hid не работает при подключении к другому типу устройства. Теперь моя цель - подключиться к интерфейсу USB (в отличие от интерфейса Hid) и читать / писать на него / с него.
На всех платформах, которые имеют доступ к USB, мы должны запрашивать интерфейсы, которые существуют для устройства USB, а затем "запрашивать" интерфейс для чтения и записи. Вот некоторый код из образца LibUsbDotNet (LibUsb - это работающая USB-библиотека C, а LibUsbDotNet оборачивает ее) https://github.com/LibUsbDotNet/LibUsbDotNet/blob/master/src/Examples/Read.Write/ReadWrite.cs.
using (var context = new UsbContext())
{
context.SetDebugLevel(LogLevel.Info);
//Get a list of all connected devices
var usbDeviceCollection = context.List();
//Narrow down the device by vendor and pid
var selectedDevice = usbDeviceCollection.FirstOrDefault(d => d.ProductId == ProductId && d.VendorId == VendorId);
//Open the device
selectedDevice.Open();
//Get the first config number of the interface
selectedDevice.ClaimInterface(selectedDevice.Configs[0].Interfaces[0].Number);
//Open up the endpoints
var writeEndpoint = selectedDevice.OpenEndpointWriter(WriteEndpointID.Ep01);
var readEnpoint = selectedDevice.OpenEndpointReader(ReadEndpointID.Ep01);
//Create a buffer with some data in it
var buffer = new byte[64];
buffer[0] = 0x3f;
buffer[1] = 0x23;
buffer[2] = 0x23;
//Write three bytes
writeEndpoint.Write(buffer, 3000, out var bytesWritten);
var readBuffer = new byte[64];
//Read some data
readEnpoint.Read(readBuffer, 3000, out var readBytes);
}
}
У меня есть ощущение, что LibUsb добивается открытия интерфейсов / конечных точек в C, как это ( https://github.com/libusb/libusb/blob/c6f3866414e8deeee19e8a9f10f20bde9cb408d3/libusb/os/windows_winusb.c#L2199). Здесь он вызывает Initialize: https://github.com/libusb/libusb/blob/c6f3866414e8deeee19e8a9f10f20bde9cb408d3/libusb/os/windows_winusb.c#L2225 где происходит сбой моего кода.
Небольшой фрагмент информации о том, что это определенно устройство WinUSB. Я вижу это здесь:
Исходя из комментариев других людей и примера кода, я вижу, что мне нужно использовать winusb.dll. Я могу вызвать CreateFile, чтобы получить дескриптор от устройства. В соответствии с другим примером кода, который я видел, следующим шагом является вызов WinUsb_Initialize. Однако, когда я вызываю это, я получаю код ошибки 8 (ERROR_NOT_ENOUGH_MEMORY). Здесь есть некоторая информация https://docs.microsoft.com/en-us/windows/desktop/api/winusb/nf-winusb-winusb_initialize. Но я не совсем понимаю, что он просит меня сделать. Это мой код до сих пор:
public override async Task InitializeAsync()
{
Dispose();
if (string.IsNullOrEmpty(DeviceId))
{
throw new WindowsException($"{nameof(DeviceDefinition)} must be specified before {nameof(InitializeAsync)} can be called.");
}
_DeviceHandle = APICalls.CreateFile(DeviceId, (APICalls.GenericWrite | APICalls.GenericRead), APICalls.FileShareRead | APICalls.FileShareWrite, IntPtr.Zero, APICalls.OpenExisting, APICalls.FileAttributeNormal | APICalls.FileFlagOverlapped, IntPtr.Zero);
var errorCode = Marshal.GetLastWin32Error();
if (errorCode > 0) throw new Exception($"Write handle no good. Error code: {errorCode}");
if (_DeviceHandle.IsInvalid) throw new Exception("Device handle no good");
var isSuccess = WinUsbApiCalls.WinUsb_Initialize(_DeviceHandle, out var interfaceHandle);
errorCode = Marshal.GetLastWin32Error();
if (!isSuccess) throw new Exception($"Initialization failed. Error code: {errorCode}");
IsInitialized = true;
RaiseConnected();
}
Вы можете клонировать ветку этого репо здесь: https://github.com/MelbourneDeveloper/Device.Net/tree/WindowsUsbDevice. Просто запустите проект Usb.Net.WindowsSample.
Я тоже попробовал это и получил точно такой же результат:
public override async Task InitializeAsync()
{
Dispose();
if (string.IsNullOrEmpty(DeviceId))
{
throw new WindowsException($"{nameof(DeviceDefinition)} must be specified before {nameof(InitializeAsync)} can be called.");
}
_DeviceHandle = APICalls.CreateFile(DeviceId, (APICalls.GenericWrite | APICalls.GenericRead), APICalls.FileShareRead | APICalls.FileShareWrite, IntPtr.Zero, APICalls.OpenExisting, APICalls.FileAttributeNormal | APICalls.FileFlagOverlapped, IntPtr.Zero);
var errorCode = Marshal.GetLastWin32Error();
if (errorCode > 0) throw new Exception($"Write handle no good. Error code: {errorCode}");
var interfaceHandle = new IntPtr();
var pDll = NativeMethods.LoadLibrary(@"C:\GitRepos\Device.Net\src\Usb.Net.WindowsSample\bin\Debug\net452\winusb.dll");
var pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "WinUsb_Initialize");
var initialize = (WinUsb_Initialize)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(WinUsb_Initialize));
var isSuccess = initialize(_DeviceHandle, ref interfaceHandle);
errorCode = Marshal.GetLastWin32Error();
if (!isSuccess) throw new Exception($"Initialization failed. Error code: {errorCode}");
IsInitialized = true;
RaiseConnected();
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate bool WinUsb_Initialize(SafeFileHandle DeviceHandle, ref IntPtr InterfaceHandle);
Я твердо верю, что что-то не так с самой реализацией WinUSB устройства. Он работает с другими библиотеками, такими как LibUsb, UWP и Android, но WinUsb, похоже, не нравится. Я попытался эту библиотеку: https://github.com/madwizard-thomas/winusbnet и она также не удается на тот же вызов с тем же кодом ошибки. Строка, в которой происходит сбой и появляется код ошибки 8, находится здесь: https://github.com/madwizard-thomas/winusbnet/blob/8f62d751a99be1e31d34b91115715d60aeff2dfc/WinUSBNet/API/WinUSBDevice.cs#L225
Что не так с моим кодом здесь? Есть ли что-то, что мне нужно сделать, чтобы выделить память для вызова WinUsb_Initialize?
Как я должен использовать winusb.dll Windows API? Какие вызовы API мне нужно сделать, чтобы запросить интерфейс и конечную точку для чтения и записи?
Это действительно помогло бы мне, если бы кто-то мог указать мне на простой пример C или C#, который читает и пишет на USB-устройство и действительно работает.
Выход WinDBG:
************* Сводка проверки пути ************** Время отклика (мс) Местоположение отложено
srv* Путь поиска символа: srv* Путь поиска исполняемого файла: ModLoad: 00000236157c0000 00000236
157c8000 Usb.Net.WindowsSample.exe ModLoad: 00007ffb62880000 00007ffb
62a61000 ntdll.dll ModLoad: 00007ffb60f40000 00007ffb
610d0000 C: \ WINDOWS \ System32 \ user32.dll ModLoad: 00007ffb5ed00000 00007ffb
5ed20000
C: \ WINDOWS \ System32 \ win32u.dll ModLoad: 00007ffb4e1b0000 00007ffb
4e214000 C: \ WINDOWS \ SYSTEM32 \ MSCOREE.DLL ModLoad: 00007ffb612a0000 00007ffb
612c8000 C: \ WINDOWS \ System32 \ GDI32.dll onecore \ windows \ core \ console \ open \ src \ renderer \ gdi \ invalidate.cpp (121) \ conhost.exe! 00007FF7169FE2AF: (абонент: 00007FF7169FF414) ReturnHr(1) tid(4230) 80070578 Неправильная ручка окна. ModLoad: 00007ffb60990000 00007ffb
60a42000
C: \ WINDOWS \ System32 \ KERNEL32.dll ModLoad: 00007ffb5f000000 00007ffb
5f192000 C: \ WINDOWS \ System32 \ gdi32full.dll ModLoad: 00007ffb60d90000 00007ffb
60f03000 C: \ WINDOWS \ System32 \ MSCTF.dll ModLoad: 00007ffb5ed80000 00007ffb
5eff3000
C: \ WINDOWS \ System32 \ KERNELBASE.dll ModLoad: 00007ffb60610000 00007ffb
606d2000 C: \ WINDOWS \ System32 \ OLEAUT32.dll ModLoad: 00007ffb60f10000 00007ffb
60f3d000 C: \ WINDOWS \ System32 \ IMM32.DLL************* Сводка проверки пути ************** Время отклика (мс) Местоположение отложено
srv* Путь поиска символа: srv* Путь поиска исполняемого файла: ModLoad: 00007ff7169f0000 00007ff7
16a8f000 conhost.exe ModLoad: 00007ffb61340000 00007ffb
62780000 C: \ WINDOWS \ System32 \ shell32.dll ModLoad: 00007ffb5cd80000 00007ffb
5cda9000
C: \ WINDOWS \ system32 \ dwmapi.dll ModLoad: 00007ffb62880000 00007ffb
62a61000 ntdll.dll ModLoad: 00007ffb5fcc0000 00007ffb
5fd09000 C: \ WINDOWS \ System32 \ cfgmgr32.dll ModLoad: 00007ffb5f530000 00007ffb
5fc3d000
C: \ WINDOWS \ System32 \ windows.storage.dll onecore \ windows \ core \ console \ open \ src \ renderer \ gdi \ invalidate.cpp (121) \ conhost.exe! 00007FF7169FE2AF: (абонент: 00007FF7169FF414) ReturnHr(2) tid(4230) 80070578 Неправильная ручка окна. ModLoad: 00007ffb61140000 00007ffb
61191000
C: \ WINDOWS \ System32 \ shlwapi.dll ModLoad: 00007ffb60990000 00007ffb
60a42000 C: \ WINDOWS \ System32 \ KERNEL32.DLL ModLoad: 00007ffb5ec30000 00007ffb
5ec41000
C: \ WINDOWS \ System32 \ kernel.appcore.dll ModLoad: 00007ffb5ed80000 00007ffb
5eff3000 C: \ WINDOWS \ System32 \ KERNELBASE.dll ModLoad: 00007ffb5ec10000 00007ffb
5ec2f000 C: \ WINDOWS \ System32 \ profapi.dll ModLoad: 00007ffb5ebc0000 00007ffb
5ec0c000
C: \ WINDOWS \ System32 \ powrprof.dll ModLoad: 00007ffb5ebb0000 00007ffb
5ebba000 C: \ WINDOWS \ System32 \ FLTLIB.DLL ModLoad: 00007ffb5f490000 00007ffb
5f52f000
C: \ WINDOWS \ System32 \ msvcp_win.dll ModLoad: 00007ffb5f1a0000 00007ffb
5f29a000 C: \ WINDOWS \ System32 \ ucrtbase.dll ModLoad: 00007ffb606e0000 00007ffb
60789000 C: \ WINDOWS \ System32 \ shcore.dll ModLoad: 00007ffb4e290000 00007ffb
4e4f9000
C: \ WINDOWS \ WinSxS \ amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.17134.472_none_fb3f9af53068156d \ comctl32.DLL ModLoad: 00007ffb5ca60000 00007ffb
5caf8000
C: \ WINDOWS \ system32 \ uxtheme.dll ModLoad: 00007ffb608f0000 00007ffb
6098e000 C: \ WINDOWS \ System32 \ msvcrt.dll ModLoad: 00007ffb601e0000 00007ffb
60304000 C: \ WINDOWS \ System32 \ RPCRT4.dll ModLoad: 00007ffb60a60000 00007ffb
60d82000
C: \ WINDOWS \ System32 \ combase.dll ModLoad: 00007ffb5fc40000 00007ffb
5fcba000 C: \ WINDOWS \ System32 \ bcryptPrimitives.dll ModLoad: 00007ffb627a0000 00007ffb
62841000 C: \ WINDOWS \ System32 \ advapi32.dll ModLoad: 00007ffb610d0000 00007ffb
6112b000
C: \ WINDOWS \ System32 \ sechost.dll ModLoad: 00007ffb57b30000 00007ffb
57bc6000 C: \ WINDOWS \ System32 \ TextInputFramework.dll (3d80.256c): исключение инструкции разрыва - код 80000003 (первый шанс) ntdll!LdrpDoDebuggerBreak+0x30: 00007ffb`6294c93c cc
Int 3
2 ответа
Вот как подключиться и читать / писать на / с USB-устройства через библиотеку WinUSB
Весь этот код содержится в репозитории Device.Net: https://github.com/MelbourneDeveloper/Device.Net. Здесь есть образец. Он автоматически переключается между Hid и UWP в зависимости от того, какое устройство подключено. https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Usb.Net.WindowsSample/Program.cs
Подключайтесь и получайте информацию
Вызовите SetupDiGetClassDevs для перечисления устройств с помощью WinUSB Guid (dee824ef-729b-4a0e-9c14-b7117d33a817). Код: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Device.Net/Windows/WindowsDeviceFactoryBase.cs#L35
Вызовите SetupDiGetDeviceInterfaceDetail, чтобы получить подробную информацию об интерфейсе и фильтре по Vid/Pid. Код: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Device.Net/Windows/WindowsDeviceFactoryBase.cs#L64
Вызовите CreateFile с возвращенным путем к устройству. Код: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L44
Вызовите WinUsb_Initialize с дескриптором, который вы получили от предыдущего вызова. Код: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L53
Вызовите WinUsb_GetDescriptor, чтобы получить информацию об устройстве. Код: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L57
Вызовите WinUsb_QueryInterfaceSettings, чтобы получить информацию об интерфейсах, принадлежащих USB-устройству. Код: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L141
Вызовите WinUsb_QueryPipe для каждого канала, который принадлежит интерфейсу. Код: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L148
Вызовите WinUsb_GetAssociatedInterface, чтобы получить другие интерфейсы, отличные от стандартных. Вероятно, в этом нет необходимости, поскольку у вас уже есть дескриптор интерфейса по умолчанию из WinUsb_Initialize. Код: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L69
Написать и прочитать
Вызовите WinUsb_WritePipe для записи массива данных. Код: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L115
Вызовите WinUsb_ReadPipe, чтобы прочитать массив данных. Код: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L98
API Calls
public static class Kernel32APICalls
{
//Abridged
#region Kernel32
[DllImport("kernel32.dll", SetLastError = true)]
public static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
#endregion
}
public static partial class WinUsbApiCalls
{
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;
[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_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);
}
Особая благодарность @benvoigt за то, что я продолжал решать эту проблему.
Ну, странность, которую вы видите около 64-байтовых передач, хорошо известна:
Разграничение передачи записи с короткими пакетами
Драйвер стека драйверов USB не накладывает те же ограничения на размер пакета при записи на устройство, которые он накладывает при чтении с устройства. Некоторые драйверы клиентов должны часто передавать небольшие объемы контрольных данных для управления своими устройствами. В таких случаях нецелесообразно ограничивать передачу данных пакетами одинакового размера. Поэтому стек драйверов не придает никакого особого значения пакетам с размером, меньшим, чем максимальный размер конечной точки во время записи данных. Это позволяет клиентскому драйверу разбить большую передачу на устройство на несколько URB любого размера, меньшего или равного максимальному.
Драйвер должен либо завершить передачу с помощью пакета, размер которого меньше максимального, либо ограничить окончание передачи с помощью пакета нулевой длины. Передача не завершена, пока драйвер не отправит пакет, меньший, чем wMaxPacketSize. Если размер передачи точно кратен максимальному, драйвер должен отправить пакет с разделителями нулевой длины, чтобы явно прекратить передачу
Ограничение передачи данных пакетами нулевой длины, как того требует спецификация USB, является обязанностью драйвера клиента. Стек драйверов USB не генерирует эти пакеты автоматически.
От передачи USB и размеров пакетов на MSDN
В остальном... в зависимости от того, объявляет ли устройство себя составным устройством или нет, загрузчик драйверов Windows выберет один или несколько драйверов устройств для подключения к устройству. Эти драйверы сами выбирают, с какой конечной точкой на устройстве разговаривать. Поэтому из пользовательского пространства обычно достаточно открыть интерфейс драйвера и начать делать ввод-вывод. Например, драйвер класса HID знает, как определить и включить конечную точку HID.
Поскольку у вас есть вещи, работающие с UWP, похоже, что у вас загружен драйвер WinUSB (так как это Шаг # 1). Поэтому вы будете использовать WinUSB API для общения с ним.
Вот документация для API C и C++ для WinUSB. Он имеет примеры настройки конечной точки и выглядит немного менее грязно, чем код libusb, который вы цитировали (хотя это может иметь отношение и к форматированию и стилю кода).