Windows API USB IO (winusb.dll)

Windows API USB IO (winusb.dll)

编辑:这个问题随着时间的推移而演变。基本问题是关于如何连接 read/write to/from Windows 中的 USB 设备。最终我在@benvoigt 的帮助下回答了这个问题。

我写了一个 Hid 库,它可以读写 to/from Hid USB 设备。它运作良好。但是,我连接的设备已从 Hid 访问切换到普通 USB。当连接到不同类型的设备时,Hid 代码不起作用。我现在的目标是连接到 USB 接口(而不是 Hid 接口)和 read/write to/from 它。

在所有接入USB的平台上,我们都要查询USB设备存在的接口,然后"claim"一个读写接口。这是来自 LibUsbDotNet 示例的一些代码(LibUsb 是一个工作的 C USB 库,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 中实现 interfaces/endpoints 的打开(https://github.com/libusb/libusb/blob/c6f3866414e8deeee19e8a9f10f20bde9cb408d3/libusb/os/windows_winusb.c#L2199) . This is where it calls 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 and it also fails on the same call with the same error code. The line it fails on and gets error code 8 is here: https://github.com/madwizard-thomas/winusbnet/blob/8f62d751a99be1e31d34b91115715d60aeff2dfc/WinUSBNet/API/WinUSBDevice.cs#L225

我这里的代码有什么问题?我需要做些什么来为 WinUsb_Initialize 调用分配内存吗?

我应该如何使用winusb.dllWindowsAPI?我需要进行哪些 API 调用来声明和读写接口或端点?

如果有人能给我指出一个简单的 C 或 C# 示例,它可以读取和写入 USB 设备并且实际工作,那将对我很有帮助

WinDBG 输出:

************* Path validation summary ************** Response Time (ms) Location Deferred
srv* Symbol search path is: srv* Executable search path is: ModLoad: 00000236157c0000 00000236157c8000 Usb.Net.WindowsSample.exe ModLoad: 00007ffb62880000 00007ffb62a61000 ntdll.dll ModLoad: 00007ffb60f40000 00007ffb610d0000 C:\WINDOWS\System32\user32.dll ModLoad: 00007ffb5ed00000 00007ffb5ed20000
C:\WINDOWS\System32\win32u.dll ModLoad: 00007ffb4e1b0000 00007ffb4e214000 C:\WINDOWS\SYSTEM32\MSCOREE.DLL ModLoad: 00007ffb612a0000 00007ffb612c8000 C:\WINDOWS\System32\GDI32.dll onecore\windows\core\console\open\src\renderer\gdi\invalidate.cpp(121)\conhost.exe!00007FF7169FE2AF: (caller: 00007FF7169FF414) ReturnHr(1) tid(4230) 80070578 Invalid window handle. ModLoad: 00007ffb60990000 00007ffb60a42000
C:\WINDOWS\System32\KERNEL32.dll ModLoad: 00007ffb5f000000 00007ffb5f192000 C:\WINDOWS\System32\gdi32full.dll ModLoad: 00007ffb60d90000 00007ffb60f03000 C:\WINDOWS\System32\MSCTF.dll ModLoad: 00007ffb5ed80000 00007ffb5eff3000
C:\WINDOWS\System32\KERNELBASE.dll ModLoad: 00007ffb60610000 00007ffb606d2000 C:\WINDOWS\System32\OLEAUT32.dll ModLoad: 00007ffb60f10000 00007ffb60f3d000 C:\WINDOWS\System32\IMM32.DLL

************* Path validation summary ************** Response Time (ms) Location Deferred
srv* Symbol search path is: srv* Executable search path is: ModLoad: 00007ff7169f0000 00007ff716a8f000 conhost.exe ModLoad: 00007ffb61340000 00007ffb62780000 C:\WINDOWS\System32\shell32.dll ModLoad: 00007ffb5cd80000 00007ffb5cda9000
C:\WINDOWS\system32\dwmapi.dll ModLoad: 00007ffb62880000 00007ffb62a61000 ntdll.dll ModLoad: 00007ffb5fcc0000 00007ffb5fd09000 C:\WINDOWS\System32\cfgmgr32.dll ModLoad: 00007ffb5f530000 00007ffb5fc3d000
C:\WINDOWS\System32\windows.storage.dll onecore\windows\core\console\open\src\renderer\gdi\invalidate.cpp(121)\conhost.exe!00007FF7169FE2AF: (caller: 00007FF7169FF414) ReturnHr(2) tid(4230) 80070578 Invalid window handle. ModLoad: 00007ffb61140000 00007ffb61191000
C:\WINDOWS\System32\shlwapi.dll ModLoad: 00007ffb60990000 00007ffb60a42000 C:\WINDOWS\System32\KERNEL32.DLL ModLoad: 00007ffb5ec30000 00007ffb5ec41000
C:\WINDOWS\System32\kernel.appcore.dll ModLoad: 00007ffb5ed80000 00007ffb5eff3000 C:\WINDOWS\System32\KERNELBASE.dll ModLoad: 00007ffb5ec10000 00007ffb5ec2f000 C:\WINDOWS\System32\profapi.dll ModLoad: 00007ffb5ebc0000 00007ffb5ec0c000
C:\WINDOWS\System32\powrprof.dll ModLoad: 00007ffb5ebb0000 00007ffb5ebba000 C:\WINDOWS\System32\FLTLIB.DLL ModLoad: 00007ffb5f490000 00007ffb5f52f000
C:\WINDOWS\System32\msvcp_win.dll ModLoad: 00007ffb5f1a0000 00007ffb5f29a000 C:\WINDOWS\System32\ucrtbase.dll ModLoad: 00007ffb606e0000 00007ffb60789000 C:\WINDOWS\System32\shcore.dll ModLoad: 00007ffb4e290000 00007ffb4e4f9000
C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.17134.472_none_fb3f9af53068156d\comctl32.DLL ModLoad: 00007ffb5ca60000 00007ffb5caf8000
C:\WINDOWS\system32\uxtheme.dll ModLoad: 00007ffb608f0000 00007ffb6098e000 C:\WINDOWS\System32\msvcrt.dll ModLoad: 00007ffb601e0000 00007ffb60304000 C:\WINDOWS\System32\RPCRT4.dll ModLoad: 00007ffb60a60000 00007ffb60d82000
C:\WINDOWS\System32\combase.dll ModLoad: 00007ffb5fc40000 00007ffb5fcba000 C:\WINDOWS\System32\bcryptPrimitives.dll ModLoad: 00007ffb627a0000 00007ffb62841000 C:\WINDOWS\System32\advapi32.dll ModLoad: 00007ffb610d0000 00007ffb6112b000
C:\WINDOWS\System32\sechost.dll ModLoad: 00007ffb57b30000 00007ffb57bc6000 C:\WINDOWS\System32\TextInputFramework.dll (3d80.256c): Break instruction exception - code 80000003 (first chance) ntdll!LdrpDoDebuggerBreak+0x30: 00007ffb`6294c93c cc
int 3

好吧,您在 64 字节传输中看到的怪事是众所周知的:

Delimiting write transfers with short packets

The USB driver stack driver does not impose the same restrictions on packet size, when writing to the device, that it imposes when reading from the device. Some client drivers must make frequent transmissions of small quantities of control data to manage their devices. It is impractical to restrict data transmissions to packets of uniform size in such cases. Therefore, the driver stack does not assign any special significance to packets of size less than the endpoint's maximum size during data writes. This allows a client driver to break a large transfer to the device into multiple URBs of any size less than or equal to the maximum.

The driver must either end the transmission by means of a packet of less than maximum size, or delimit the end of the transmission by means of a zero-length packet. The transmission is not complete until the driver sends a packet smaller than wMaxPacketSize. If the transfer size is an exact multiple of the maximum, the driver must send a zero-length delimiting packet to explicitly terminate the transfer

Delimiting the data transmission with zero-length packets, as required by the USB specification, is the responsibility of the client driver. The USB driver stack does not generate these packets automatically.

来自 MSDN 上的 USB Transfer and Packet Sizes


至于其他...根据设备是否声明自己为复合设备,Windows 的驱动程序加载程序将选择一个或多个设备驱动程序连接到设备。这些驱动程序负责选择要与设备上的哪个端点进行通信。所以从用户空间,通常打开驱动程序界面并开始做 I/O 就足够了。例如,HID class 驱动程序知道如何识别和启用 HID 端点。

由于您有使用 UWP 的东西,您似乎可能已经加载了 WinUSB 驱动程序(因为那是 Step #1)。因此,您将使用 WinUSB API 与其对话。

这是 the C and C++ API for WinUSB 的文档。它有端点设置的示例,看起来比您引用的 libusb 代码更整洁(尽管这可能也与代码格式和样式有关)。

以下是如何通过 WinUSB 库连接和 read/write to/from USB 设备

所有这些代码都包含在 Device.Net 存储库中:https://github.com/MelbourneDeveloper/Device.Net . There is a sample here. It automatically switched between Hid, and UWP depending on which device is plugged in. https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Usb.Net.WindowsSample/Program.cs

连接并获取信息

读写

API 调用

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 让我继续解决这个问题。