Edit: This question evolved over time. The basic question was about how to connect to a and read/write to/from a USB device in Windows. Eventually I answered the question with the help of @benvoigt.
I have written a Hid library which writes and reads to/from Hid USB devices. It works well. However, the device I am connecting to has switched from Hid access to vanilla USB. The Hid code does not work when connecting to the different type of device. My aim now is to connect to the USB interface (as opposed to the Hid interface) and read/write to/from it.
On all platforms that access USB, we have to query the interfaces that exist for the USB device, and then "claim" an interface for reading and writing. Here is some code from a LibUsbDotNet sample (LibUsb is a working C USB library and LibUsbDotNet wraps it) 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);
}
}
I have a feeling that LibUsb is achieving the opening of interfaces/endpoints in C like this (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 which is where my code is failing.
A little snippet of information is that this is definitely a WinUSB device. I can see that here:
Based on other people's comments and sample code, I can see that I need to use winusb.dll. I am able to call CreateFile to get a handle from the device. According to other sample code I have seen, the next step is to call WinUsb_Initialize. However, when I call this, I get an error code of 8 (ERROR_NOT_ENOUGH_MEMORY). There is some information here https://learn.microsoft.com/en-us/windows/desktop/api/winusb/nf-winusb-winusb_initialize . But, I don't quite understand what it is asking me to do. This is my code so far:
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();
}
You can clone the branch of this repo here: https://github.com/MelbourneDeveloper/Device.Net/tree/WindowsUsbDevice. Just run the Usb.Net.WindowsSample project.
I also tried this and got exactly the same result:
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);
I strongly believe that there is something wrong with the device's WinUSB implementation itself. It works with other libraries like LibUsb, UWP, and Android, but WinUsb doesn't seem to like it. I tried this library: 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
What is wrong with my code here? Is there something I need to do to allocate memory for the WinUsb_Initialize call?
How should I use the winusb.dll Windows API? What API calls do I need to make to claim and interface or endpoint for reading and writing?
It would really help me if someone could point me to a simple C or C# sample that reads and writes to a USB device and actually works.
WinDBG output:
************* 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
Here's how to connect to and read/write to/from a USB device via the WinUSB library
All this code is contained in the Device.Net repo: 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
Connect and get Information
Call SetupDiGetClassDevs to enumerate devices with the WinUSB Guid (dee824ef-729b-4a0e-9c14-b7117d33a817). Code: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Device.Net/Windows/WindowsDeviceFactoryBase.cs#L35
Call SetupDiGetDeviceInterfaceDetail to get details about the interface and filter by Vid/Pid. Code: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Device.Net/Windows/WindowsDeviceFactoryBase.cs#L64
Call CreateFile with the returned Device Path. Code: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L44
Call WinUsb_Initialize with the handle you got from the previous call. Code: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L53
Call WinUsb_GetDescriptor to get information about the device. Code: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L57
Call WinUsb_QueryInterfaceSettings to get information about the interfaces belonging to the USB device. Code: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L141
Call WinUsb_QueryPipe for each pipe that belongs to the interface. Code: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L148
Call WinUsb_GetAssociatedInterface to get other interfaces other than the default. This will probably not be necessary because you will already have the default interface handle from WinUsb_Initialize. Code: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L69
Write and Read
Call WinUsb_WritePipe to write an array of data. Code: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L115
Call WinUsb_ReadPipe to read an array of data. Code: https://github.com/MelbourneDeveloper/Device.Net/blob/58aca0de118576ba89ec7437b29176d9bdd90aea/src/Usb.Net/Windows/WindowsUsbDevice.cs#L98
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);
}
Special thanks to @benvoigt for keeping me moving on this problem.