【发布时间】: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' 设备上。