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 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:
(caller: 00007FF7169FF414) ReturnHr(1) tid(4230) 80070578 Invalid
window handle. 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
************* Path validation summary ************** Response Time (ms) Location Deferred
srv* Symbol search path is: srv* Executable search path is: 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:
(caller: 00007FF7169FF414) ReturnHr(2) tid(4230) 80070578 Invalid
window handle. 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): 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
连接并获取信息
调用 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 调用
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 让我继续解决这个问题。
编辑:这个问题随着时间的推移而演变。基本问题是关于如何连接 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 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: (caller: 00007FF7169FF414) ReturnHr(1) tid(4230) 80070578 Invalid window handle. 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************* Path validation summary ************** Response Time (ms) Location Deferred
srv* Symbol search path is: srv* Executable search path is: 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: (caller: 00007FF7169FF414) ReturnHr(2) tid(4230) 80070578 Invalid window handle. 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): 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
连接并获取信息
调用 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 调用
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 让我继续解决这个问题。