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: 00000236 157c0000 00000236 157c8000 Usb.Net.WindowsSample.exe ModLoad: 00007ffb 62880000 00007ffb 62a61000 ntdll.dll ModLoad: 00007ffb 60f40000 00007ffb 610d0000 C: \ WINDOWS \ System32 \ user32.dll ModLoad: 00007ffb 5ed00000 00007ffb 5ed20000
C: \ WINDOWS \ System32 \ win32u.dll ModLoad: 00007ffb 4e1b0000 00007ffb4e214000 C: \ WINDOWS \ SYSTEM32 \ MSCOREE.DLL ModLoad: 00007ffb 612a0000 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: 00007ffb 60990000 00007ffb 60a42000
C: \ WINDOWS \ System32 \ KERNEL32.dll ModLoad: 00007ffb 5f000000 00007ffb 5f192000 C: \ WINDOWS \ System32 \ gdi32full.dll ModLoad: 00007ffb 60d90000 00007ffb 60f03000 C: \ WINDOWS \ System32 \ MSCTF.dll ModLoad: 00007ffb 5ed80000 00007ffb 5eff3000
C: \ WINDOWS \ System32 \ KERNELBASE.dll ModLoad: 00007ffb 60610000 00007ffb 606d2000 C: \ WINDOWS \ System32 \ OLEAUT32.dll ModLoad: 00007ffb 60f10000 00007ffb 60f3d000 C: \ WINDOWS \ System32 \ IMM32.DLL

************* Сводка проверки пути ************** Время отклика (мс) Местоположение отложено
srv* Путь поиска символа: srv* Путь поиска исполняемого файла: ModLoad: 00007ff7 169f0000 00007ff7 16a8f000 conhost.exe ModLoad: 00007ffb 61340000 00007ffb 62780000 C: \ WINDOWS \ System32 \ shell32.dll ModLoad: 00007ffb 5cd80000 00007ffb 5cda9000
C: \ WINDOWS \ system32 \ dwmapi.dll ModLoad: 00007ffb 62880000 00007ffb 62a61000 ntdll.dll ModLoad: 00007ffb 5fcc0000 00007ffb 5fd09000 C: \ WINDOWS \ System32 \ cfgmgr32.dll ModLoad: 00007ffb 5f530000 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: 00007ffb 61140000 00007ffb 61191000
C: \ WINDOWS \ System32 \ shlwapi.dll ModLoad: 00007ffb 60990000 00007ffb 60a42000 C: \ WINDOWS \ System32 \ KERNEL32.DLL ModLoad: 00007ffb 5ec30000 00007ffb 5ec41000
C: \ WINDOWS \ System32 \ kernel.appcore.dll ModLoad: 00007ffb 5ed80000 00007ffb 5eff3000 C: \ WINDOWS \ System32 \ KERNELBASE.dll ModLoad: 00007ffb 5ec10000 00007ffb 5ec2f000 C: \ WINDOWS \ System32 \ profapi.dll ModLoad: 00007ffb 5ebc0000 00007ffb 5ec0c000
C: \ WINDOWS \ System32 \ powrprof.dll ModLoad: 00007ffb 5ebb0000 00007ffb 5ebba000 C: \ WINDOWS \ System32 \ FLTLIB.DLL ModLoad: 00007ffb 5f490000 00007ffb 5f52f000
C: \ WINDOWS \ System32 \ msvcp_win.dll ModLoad: 00007ffb 5f1a0000 00007ffb 5f29a000 C: \ WINDOWS \ System32 \ ucrtbase.dll ModLoad: 00007ffb 606e0000 00007ffb 60789000 C: \ WINDOWS \ System32 \ shcore.dll ModLoad: 00007ffb 4e290000 00007ffb 4e4f9000
C: \ WINDOWS \ WinSxS \ amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.17134.472_none_fb3f9af53068156d \ comctl32.DLL ModLoad: 00007ffb 5ca60000 00007ffb 5caf8000
C: \ WINDOWS \ system32 \ uxtheme.dll ModLoad: 00007ffb 608f0000 00007ffb 6098e000 C: \ WINDOWS \ System32 \ msvcrt.dll ModLoad: 00007ffb 601e0000 00007ffb 60304000 C: \ WINDOWS \ System32 \ RPCRT4.dll ModLoad: 00007ffb 60a60000 00007ffb 60d82000
C: \ WINDOWS \ System32 \ combase.dll ModLoad: 00007ffb 5fc40000 00007ffb 5fcba000 C: \ WINDOWS \ System32 \ bcryptPrimitives.dll ModLoad: 00007ffb 627a0000 00007ffb 62841000 C: \ WINDOWS \ System32 \ advapi32.dll ModLoad: 00007ffb 610d0000 00007ffb 6112b000
C: \ WINDOWS \ System32 \ sechost.dll ModLoad: 00007ffb 57b30000 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

Подключайтесь и получайте информацию

Написать и прочитать

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, который вы цитировали (хотя это может иметь отношение и к форматированию и стилю кода).

Другие вопросы по тегам