Search code examples
c#winapiusbhidwinusb

Windows API USB IO (winusb.dll)


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:

enter image description 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 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


Solution

  • 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

    Write and Read

    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.