Getting a handle on usbprint.sys

So its Thursday morning at work and I decide that since the Mac OS X version of our software can print directly to USB printers bypassing the operating system, its about time we added that functionality to the Linux and Windows versions.

Start on the Linux version

A quick scan through the /usr/src/linux/drivers/usb/devio.c source file reveals all about the usbdevfs file system and the ioctls required. Browse through the USB spec, a few calls to open() and ioctl(), and urb here, an urb there and I’m all done. Piece of cake.

Start on the Windows version

Shudder… Past experiences with Windows and anything to do with low level access to devices has taught me that I am in for the long haul. Each time I try to do something like this it always take 3 – 4 days or excruciating effort and unbearable pain. Oh well, I strap myself to my trusty Aeron chair and away I go.

Now from the MSDN documentation I manage to surmise that as soon as a USB printer is plugged in, the “plug and pray” subsystem loads up the usbprint.sys device driver and passes control over to it. In turn usbprint.sys creates a device node, initialises it and then sets up a device interface so user space programs can access it using the usual device semantics.

Great I think. I’ll just call CreateFile() to open the device and then use WriteFile() to send data to it. Couldn’t be easier. “clickety clack”, the code just flies from my fingers. CreateFile(name….). hmm.. what’s the name of the device. If it was Linux it would be /dev/usblp0 or something like that. Now what is it for Windows. A scan through the MSDN documentation reveals nothing. A search of google produces lots of hits to people asking the same question, but no answers. “Oh, oh this is going to be bad I think”. If there are no answers out there it means it is a closely guarded Microsoft secret and I am going to have to do some deep digging myself.

A more methodical scan of the MSDN docs and Google at least hints to what calls I need to make. In essence you do the following.

  1. Call SetupDiGetClassDevs(interface_guid, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE) to get a list of devices.
  2. Call SetupDiEnumDeviceInterfaces() to get a list of available device interfaces.
  3. Call SetupDiGetDeviceInterfaceDetail() to fetch information for the actual device interface we are going to use.
  4. Use the DevicePath member of the PSP_DEVICE_INTERFACE_DETAIL_DATA structure returned by the SetupDiGetDeviceInterfaceDetail() call as the devicename/filename in the CreateFile() call.
  5. Call WriteFile() repeatedly to send data to the device.
  6. Call CloseHandle() to close the device

So in order to be able to find the name of the device, I need to find the GUID of the interface that usbprint.sys creates when it adds the devicenode to the system. Search… search… search… nothing. Search some more, nothing. Look through the Windows SDK and DDK include files with grep. Still nothing. Oh well up comes trusty old regedit, everything windows does is in the registry, and I start to wade through the myriad of nodes and entries for the GUID. After a few hours of despair I find it. The magic number is 0×28d78fad, 0×5a12, 0×11D1, 0xae, 0×5b, 0×00, 0×00, 0xf8, 0×03, 0xa8, 0xc2. You’d think Microsoft would have included it in one of the .h files (devguid.h or usbprint.h) just like they did the interface GUID for keyboards, mice, hid usb devices, usbcam devices etc. etc. etc.

Oh well at least I can now call open CreateFile and Write to the printer. So here we go:

HANDLE usbHandle = CreateFile(interfacename, GENERIC_WRITE, FILE_SHARE_READ,
                              NULL, OPEN_EXISTING, 0, NULL);
WriteFile(usbhandle, buf, numbytes, &byteswrtten);

The WriteFile call fails and GetLastError() returns 1, ie. the operation is not supported.

More searching, reading, nothing turns up. It keeps failing. I remember that the DDK includes source code for the local portmonitor so I take a look to see how it calls CreateFile for the local port interface. I modify mine to be the same and try again.

HANDLE usbHandle = CreateFile(interfacename, GENERIC_WRITE, FILE_SHARE_READ,
                              NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL |
                              FILE_FLAG_SEQUENTIAL_SCAN, NULL);
WriteFile(usbhandle, buf, numbytes, &byteswrtten);

Success, the printer starts outputting my job. What is different above? Well replacing OPEN_EXISTING with OPEN_ALWAYS seems to have forced the driver to start the device so that the WriteFile interface is available. Previously OPEN_EXISTING would not try and create a file so the device would not start. At least that is what I think.

4 days later and I am all done.

So here is a quick snippet of some of my code to help others out there that might have the same problem. You will need both the windows SDK and DDK installed and configured correctly for this code to compile. You will also need to link your executable with the setupapi library.

Below I have included the code as both an attachment and inline. It is not meant to compile out of the box. It is only there as a guide.

usbprint.c

/* Code to find the device path for a usbprint.sys controlled
* usb printer and print to it
*/

#include <usb.h>
#include <usbiodef.h>
#include <usbioctl.h>
#include <usbprint.h>
#include <setupapi.h>
#include <devguid.h>
#include <wdmguid.h>

/* This define is required so that the GUID_DEVINTERFACE_USBPRINT variable is
 * declared an initialised as a static locally, since windows does not include it
 * in any of its libraries
 */

#define SS_DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
static const GUID name \
= { l, w1, w2, { b1, b2,  b3,  b4,  b5,  b6,  b7,  b8 } }

SS_DEFINE_GUID(GUID_DEVINTERFACE_USBPRINT, 0x28d78fad, 0x5a12, 0x11D1, 0xae,
               0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2);

void SomeFunctionToWriteToUSB()
{
  HDEVINFO devs;
  DWORD devcount;
  SP_DEVINFO_DATA devinfo;
  SP_DEVICE_INTERFACE_DATA devinterface;
  DWORD size;
  GUID intfce;
  PSP_DEVICE_INTERFACE_DETAIL_DATA interface_detail;

  intfce = GUID_DEVINTERFACE_USBPRINT;
  devs = SetupDiGetClassDevs(&intfce, 0, 0, DIGCF_PRESENT |
                             DIGCF_DEVICEINTERFACE);
  if (devs == INVALID_HANDLE_VALUE) {
    return;
  }
  devcount = 0;
  devinterface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
  while (SetupDiEnumDeviceInterfaces(devs, 0, &intfce, devcount, &devinterface)) {
    /* The following buffers would normally be malloced to he correct size
     * but here we just declare them as large stack variables
     * to make the code more readable
     */
    char driverkey[2048];
    char interfacename[2048];
    char location[2048];
    char description[2048];

    /* If this is not the device we want, we would normally continue onto the
     * next one or so something like
     * if (!required_device) continue; would be added here
     */
    devcount++;
    size = 0;
    /* See how large a buffer we require for the device interface details */
    SetupDiGetDeviceInterfaceDetail(devs, &devinterface, 0, 0, &size, 0);
    devinfo.cbSize = sizeof(SP_DEVINFO_DATA);
    interface_detail = calloc(1, size);
    if (interface_detail) {
      interface_detail->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA);
      devinfo.cbSize = sizeof(SP_DEVINFO_DATA);
      if (!SetupDiGetDeviceInterfaceDetail(devs, &devinterface, interface_detail,
                                           size, 0, &devinfo)) {
    free(interface_detail);
    SetupDiDestroyDeviceInfoList(devs);
    return;
      }
      /* Make a copy of the device path for later use */
      strcpy(interfacename, interface_detail->DevicePath);
      free(interface_detail);
      /* And now fetch some useful registry entries */
      size = sizeof(driverkey);
      driverkey[0] = 0;
      if (!SetupDiGetDeviceRegistryProperty(devs, &devinfo, SPDRP_DRIVER, &dataType,
                                            (LPBYTE)driverkey, size, 0)) {
    SetupDiDestroyDeviceInfoList(devs);
    return;
      }
      size = sizeof(location);
      location[0] = 0;
      if (!SetupDiGetDeviceRegistryProperty(devs, &devinfo,
                                            SPDRP_LOCATION_INFORMATION, &dataType,
                                            (LPBYTE)location, size, 0)) {
    SetupDiDestroyDeviceInfoList(devs);
    return;
      }
      usbHandle = CreateFile(interfacename, GENERIC_WRITE, FILE_SHARE_READ,
                 NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL |
                 FILE_FLAG_SEQUENTIAL_SCAN, NULL);
      if (usbHandle != INVALID_HANDLE_VALUE) {
    /* Now perform all the writing to the device ie.
     * while (some condition) WriteFile(usbHandle, buf, size, &bytes_written);
     */
    CloseHandle(usbHandle);
      }
    }
  }
  SetupDiDestroyDeviceInfoList(devs);
}

Technorati Tags:

109 Responses to “Getting a handle on usbprint.sys”

  1. James says:

    Thanks so much. This was so obscure and hard to find. I spent 3 days and almost got to the point of writing my own driver until I found the device class ID here and found the device path. It should have been that easy. Maybe MS will learn and at least publish this at some point.

  2. Jyotiranjan says:

    Hi,
    Thanks for this post.I have implemented the same way as mentioned by you.I’m doing USB communication for my Barcode Printer.The code i’m writing in VS2005.Its compiling fine.I have included SetupApi.lib & SetupApi.h into my project but not all the header file ( #include
    #include
    #include
    #include
    #include
    #include
    )mentioned by you. I have installed the DDK and SDK given by microsoft in my PC.But the problem i’m facing is in this line :-
    while (SetupDiEnumDeviceInterfaces(devs, 0, &intfce, devcount, &devinterface))

    While debugging,its not entering into the while loop .Is the function will return true only when the devide is attached? Why i’m asking this is,becoz i have not connected my printer to the USB port.Kindly suggest some answer.If printer device is not attached, kindly tell me how to simulate the condition?? Pls. help.

  3. SetupDiEnumDeviceInterfaces() will only find usbprinter device nodes when a printer is attached. There is no way to simulate that, you simply have to attach a printer. There is no getting around that.

  4. Jyotiranjan says:

    Dear Sir,
    Thanks for giving code.By following you , my writefile function is returning true with Byte to write is equal to Byte written (these are two variables,which i’m checking after write file API).But i’m not able to print.The same string i’m able to print through command prompt.it means that the command that has to sent to the printer is correct,but why its not printing ?.Kindly note that my Zebra barcode printer driver is installed and ok.

  5. MaybeStop says:

    I found the ClassGUID is this the required GUID I need?

    Can I just eNumerate and use the Symbolic address and just write to the port with the required data.

    I am new to this so any help would be great.
    Trying to use the USB to Parallel as a data port.

    Thanks

  6. need help says:

    any sample doin this in C#/vb .net ?

    I ‘ll use it for sending raw data to lx300 through usb to paralel converter …

    thanks

  7. The Dewd says:

    Peter, thanks so much for posting this code here. I stumbled across it after googling for 3 days to try and find some way to read and write raw data to a usb printer in C#. I can’t post the code on here because my client would lose their freakin’ mind if they ever found out I did but I will tell anyone wanting to get it working in .NET to go here . There is some sample C# code and all the structure and constant definitions that are not included here (you have to look for them but they are on that site.) It took me an additional 2 days after finding this page to get it working but most of that time was due to bone headed mistakes on my part.

    Thanks again for this invaluable resource!

  8. The Dewd says:

    Sorry, the website address in my previous post was ommited. This is the url http://www.pinvoke.net/default.aspx/setupapi/SetupDiEnumDeviceInterfaces.html

  9. Old Duster says:

    Great work Peter and others.
    In case it helps anyone, here’s my VB6 conversion.
    (Not elegant yet, but appears to work).

    Option Explicit

    Private Const DIGCF_PRESENT As Integer = &H2
    Private Const DIGCF_DEVICEINTERFACE As Integer = &H10
    Private Const DIGCF_ALLCLASSES As Integer = &H4
    Private Const GENERIC_READ = &H80000000
    Private Const GENERIC_WRITE = &H40000000
    Private Const FILE_FLAG_SEQUENTIAL_SCAN = &H8000000
    Private Const FILE_ATTRIBUTE_HIDDEN = &H2
    Private Const FILE_ATTRIBUTE_NORMAL = &H80
    Private Const FILE_SHARE_READ = &H1
    Private Const FILE_SHARE_WRITE = &H2
    Private Const OPEN_ALWAYS = 4
    Private Const OPEN_EXISTING = 3

    Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
    End Type

    Private Type Device_Interface_Data
    cbSize As Long
    InterfaceClassGuid As GUID
    Flags As Long
    ReservedPtr As Long
    End Type

    Private Type Device_Interface_Detail
    cbSize As Long
    DataPath(256) As Byte
    End Type

    Private Declare Function SetupDiGetDeviceInterfaceDetail _
    Lib “setupapi.dll” Alias “SetupDiGetDeviceInterfaceDetailA” _
    (ByVal DeviceInfoSet As Long, DeviceInterfaceData As Any, _
    DeviceInterfaceDetailData As Any, _
    ByVal DeviceInterfaceDetailDataSize As Long, _
    RequiredSize As Long, ByVal DeviceInfoData As Long) As Long

    Private Declare Function CreateFile Lib “kernel32″ Alias “CreateFileA” _
    (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, _
    ByVal dwShareMode As Long, lpSecurityAttributes As Any, _
    ByVal dwCreationDisposition As Long, _
    ByVal dwFlagsAndAttributes As Long, _
    ByVal hTemplateFile As Long) As Long

    Private Declare Sub CloseHandle Lib “kernel32″ _
    (ByVal HandleToClose As Long)

    Private Declare Function ReadFile Lib “kernel32″ _
    (ByVal Handle As Long, ByVal BufferPtr As Long, _
    ByVal ByteCount As Long, BytesReturnedPtr As Long, _
    ByVal OverlappedPtr As Long) As Long

    Private Declare Function WriteFile Lib “kernel32″ _
    (ByVal Handle As Long, Buffer As String, _
    ByVal ByteCount As Long, BytesReturnedPtr As Long, _
    ByVal OverlappedPtr As Long) As Long

    Private Declare Function SetupDiGetClassDevs Lib “setupapi.dll” _
    Alias “SetupDiGetClassDevsA” (GuidPtr As Long, _
    ByVal EnumPtr As Long, ByVal hwndParent As Long, _
    ByVal Flags As Long) As Long

    Private Declare Function SetupDiDestroyDeviceInfoList _
    Lib “setupapi.dll” _
    (ByVal DeviceInfoSet As Long) As Boolean

    Private Declare Function SetupDiEnumDeviceInterfaces _
    Lib “setupapi.dll” (ByVal Handle As Long, _
    ByVal InfoPtr As Long, GuidPtr As Long, _
    ByVal MemberIndex As Long, _
    InterfaceDataPtr As Long) As Boolean

    Private Sub Command1_Click()
    Text1.Text = SendToUsbPrinter(”Hello world.”)
    End Sub

    Function SendToUsbPrinter(PrintOut As String) As Boolean
    Dim PrnGuid As GUID
    Dim Success As Long, Ret As Long
    Dim Openned As Boolean
    Dim Buffer(256) As Byte
    Dim DeviceInterfaceData As Device_Interface_Data
    Dim FunctionClassDeviceData As Device_Interface_Detail
    Dim PnPHandle As Long, BytesReturned As Long
    Dim I As Long
    Dim DeviceName As String, DevIndex As Long, DeviceHandle As Long
    Dim ReqdSize As Long
    Dim BytesWritten As Long

    ‘ \\?\usb#vid_0a5f&pid_000a#41a081100503#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}
    PrnGuid.Data1 = &H28D78FAD
    PrnGuid.Data2 = &H5A12
    PrnGuid.Data3 = &H11D1
    PrnGuid.Data4(0) = &HAE
    PrnGuid.Data4(1) = &H5B
    PrnGuid.Data4(2) = &H0
    PrnGuid.Data4(3) = &H0
    PrnGuid.Data4(4) = &HF8
    PrnGuid.Data4(5) = &H3
    PrnGuid.Data4(6) = &HA8
    PrnGuid.Data4(7) = &HC2
    PnPHandle = SetupDiGetClassDevs(PrnGuid.Data1, 0, 0, _
    DIGCF_PRESENT Or DIGCF_DEVICEINTERFACE)
    SendToUsbPrinter = False
    If (PnPHandle = -1) Then
    MsgBox “Could not attach to PnP node”
    Else
    DeviceInterfaceData.cbSize = Len(DeviceInterfaceData)
    DevIndex = 0
    ‘ Should be a Do While -> looking for the correct device-name…
    If SetupDiEnumDeviceInterfaces(PnPHandle, 0, PrnGuid.Data1, _
    DevIndex, DeviceInterfaceData.cbSize) Then
    FunctionClassDeviceData.cbSize = 5
    Success = SetupDiGetDeviceInterfaceDetail(PnPHandle, _
    DeviceInterfaceData, FunctionClassDeviceData, _
    UBound(FunctionClassDeviceData.DataPath), _
    BytesReturned, 0)
    If Success = 0 Then
    MsgBox “Could not get the name of this device”
    Else
    DeviceName = “”
    I = 0
    Do While FunctionClassDeviceData.DataPath(I) 0
    DeviceName = DeviceName & _
    Chr$(FunctionClassDeviceData.DataPath(I))
    I = I + 1
    Loop
    Debug.Print DeviceName
    DeviceHandle = CreateFile(DeviceName, _
    GENERIC_WRITE, FILE_SHARE_READ, _
    0, OPEN_ALWAYS, _
    FILE_ATTRIBUTE_NORMAL + FILE_FLAG_SEQUENTIAL_SCAN, _
    0)
    If (DeviceHandle = -1) Then
    Debug.Print “Open failed on ” & DeviceName
    Else
    Ret = WriteFile(DeviceHandle, PrintOut, _
    Len(PrintOut), BytesWritten, 0)
    Debug.Print “Sent ” & BytesWritten & ” bytes.”
    SendToUsbPrinter = True
    CloseHandle DeviceHandle
    End If
    End If
    Else
    MsgBox “Device not connected”
    End If
    SetupDiDestroyDeviceInfoList (PnPHandle)
    End If
    End Function

Leave a Reply