【问题标题】:Windows API USB IO (winusb.dll)Windows API USB IO (winusb.dll)
【发布时间】:2019-05-10 09:49:46
【问题描述】:

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

我编写了一个 Hid 库,它可以向 Hid USB 设备写入和读取数据。它运作良好。但是,我连接的设备已从隐藏访问切换到普通 USB。连接到不同类型的设备时,Hid 代码不起作用。我现在的目标是连接到 USB 接口(而不是 Hid 接口)并对其进行读/写。

在所有访问 USB 的平台上,我们必须查询 USB 设备存在的接口,然后“声明”一个用于读取和写入的接口。这是来自 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 正在像这样 (https://github.com/libusb/libusb/blob/c6f3866414e8deeee19e8a9f10f20bde9cb408d3/libusb/os/windows_winusb.c#L2199) 实现 C 中接口/端点的开放。这就是它调用 Initialize: https://github.com/libusb/libusb/blob/c6f3866414e8deeee19e8a9f10f20bde9cb408d3/libusb/os/windows_winusb.c#L2225 的地方,这是我的代码失败的地方。

sn-p 的一点信息是,这绝对是一个 WinUSB 设备。我可以在这里看到:

根据别人的cmets和示例代码,可以看出需要用到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();
    }

您可以在此处克隆此 repo 的分支: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 输出:

****************** 路径验证摘要 ************** 响应时间 (ms) 位置延迟
srv* 符号搜索路径为:srv* 可执行搜索路径为:ModLoad: 00000236157c0000 00000236157c8000 Usb.Net.WindowsSample.exe 模组加载:00007ffb62880000 00007ffb62a61000 ntdll.dll 模组加载: 00007ffb60f40000 00007ffb610d0000 C:\WINDOWS\System32\user32.dll 模组加载: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: (来电者:00007FF7169FF414)ReturnHr(1) tid(4230) 80070578 无效 窗把手。模组加载:00007ffb60990000 00007ffb60a42000
C:\WINDOWS\System32\KERNEL32.dll ModLoad: 00007ffb5f000000 00007ffb5f192000 C:\WINDOWS\System32\gdi32full.dll ModLoad: 00007ffb60d90000 00007ffb60f03000 C:\WINDOWS\System32\MSCTF.dll 模组加载:00007ffb5ed80000 00007ffb5eff3000
C:\WINDOWS\System32\KERNELBASE.dll 模块加载:00007ffb60610000 00007ffb606d2000 C:\WINDOWS\System32\OLEAUT32.dll 模块加载: 00007ffb60f10000 00007ffb60f3d000 C:\WINDOWS\System32\IMM32.DLL

****************** 路径验证摘要 ************** 响应时间 (ms) 位置延迟
srv* 符号搜索路径为:srv* 可执行搜索路径为:ModLoad: 00007ff7169f0000 00007ff716a8f000 conhost.exe 模组加载: 00007ffb61340000 00007ffb62780000 C:\WINDOWS\System32\shell32.dll 模组加载: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: (来电者:00007FF7169FF414)ReturnHr(2) tid(4230) 80070578 无效 窗把手。模组加载: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 模组加载: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 模组加载:00007ffb4e290000 00007ffb4e4f9000
C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.17134.472_none_fb3f9af53068156d\comctl32.DLL 模组加载:00007ffb5ca60000 00007ffb5caf8000
C:\WINDOWS\system32\uxtheme.dll ModLoad: 00007ffb608f0000 00007ffb6098e000 C:\WINDOWS\System32\msvcrt.dll ModLoad: 00007ffb601e0000 00007ffb60304000 C:\WINDOWS\System32\RPCRT4.dll 模组加载:00007ffb60a60000 00007ffb60d82000
C:\WINDOWS\System32\combase.dll ModLoad: 00007ffb5fc40000 00007ffb5fcba000 C:\WINDOWS\System32\bcryptPrimitives.dll ModLoad: 00007ffb627a0000 00007ffb62841000 C:\WINDOWS\System32\advapi32.dll 模组加载:00007ffb610d0000 00007ffb6112b000
C:\WINDOWS\System32\sechost.dll ModLoad: 00007ffb57b30000 00007ffb57bc6000 C:\WINDOWS\System32\TextInputFramework.dll (3d80.256c):中断指令异常 - 代码 80000003(第一个 机会)ntdll!LdrpDoDebuggerBreak+0x30: 00007ffb`6294c93c cc
诠释 3

【问题讨论】:

  • 您如何确保您的访问对齐并使用扇区大小的精确倍数。为什么没有minimal reproducible example
  • 我认为这个问题的答案是我已经尝试了我所知道的所有扇区大小(数据缓冲区大小)并且它们都产生了相同的结果。部分问题是设备期望的大小是 64 字节,但如果我指定 64,则会出现错误。我必须发送 64,但指定 65。Windows IO 存在某种问题。要回答您的第二个问题,除非您拥有 Trezor 硬件钱包,否则无法验证该示例。我的问题是一般性的,而不是具体的:“对普通 USB 设备与 Hid 设备之间的区别有何建议?”
  • 一般问题不适合在这里。 “有没有人有任何一般性建议”不是一个合适的问题。就对齐和缓冲区大小而言,您不知道使用了什么,因为您已将其委托给可能进行自己缓冲的流。直接使用 ReadFile。在 C++ 中要容易得多。至少在调试和学习的同时。
  • 为什么要直接使用ReadFile?我试过了,还是不行,为什么这样更好呢?
  • InterfaceHandle 应该是 out,而不是 ref,但这不会改变您的问题。除此之外,声明看起来还不错。我无法重现错误 8。当我尝试在我的 PC 上打开设备时,我只收到错误 50 (ERROR_NOT_SUPPORTED)。也许问题出在这个特定的 USB 'trezor' 设备上。

标签: c# winapi usb hid winusb


【解决方案1】:

以下是如何通过 WinUSB 库连接和读取/写入 USB 设备

所有这些代码都包含在 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 调用

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

【讨论】:

    【解决方案2】:

    嗯,您在 64 字节传输中看到的怪异现象是众所周知的:

    用短数据包分隔写传输

    USB 驱动程序堆栈 驱动程序不会对数据包大小施加相同的限制,当 写入设备,它在从设备读取时强加。 一些客户端驱动程序必须频繁传输小 大量的控制数据来管理他们的设备。这是不切实际的 限制数据传输到统一大小的数据包 案例。因此,驱动程序堆栈没有分配任何特殊的 对大小小于端点最大大小的数据包的重要性 在数据写入期间。这允许客户端驱动程序打破一个大 传输到设备成多个任意大小的 URB,小于或 等于最大值。

    驱动程序必须通过以下数据包结束传输 小于最大大小,或通过 指零长度数据包。传输未完成,直到 驱动程序发送一个小于 wMaxPacketSize 的数据包。 如果转让 大小是最大值的精确倍数,驱动程序必须发送一个 用于显式终止传输的零长度定界数据包

    根据需要用零长度数据包分隔数据传输 根据 USB 规范,是客户端驱动程序的责任。 USB 驱动程序堆栈不会自动生成这些数据包。

    来自 MSDN 上的USB Transfer and Packet Sizes


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

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

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

    【讨论】:

    猜你喜欢
    • 2023-04-04
    • 2012-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-29
    • 1970-01-01
    • 2011-02-24
    • 2011-12-01
    相关资源
    最近更新 更多