Raw printing directly to a USB printer, bypassing Windows spooler

55,322

Solution 1

Thanks for the comments.

After some more digging around, I found this interesting article on using Windows printer functions provided by usbprint.sys. With a bit of hacking the sample code there seemed to work. I think I'll take this route.

There is the final code given in the article:

/* 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);
}

Thanks again for the suggestions.

Solution 2

Is there a way to bypass the spooler and output data straight to this USB printer?

Yes, absolutely. It's built into most OSs, printing raw via USB is just a bit less obvious than Ethernet and COM/LPT. Note, many applications, such as notepad are incapable of printing raw, so your application needs to support this as well.

  1. Install the appropriate driver for your USB printer. Check the printer properties to see which USB port it is using. It may be USB001, etc.
  2. Using the Devices and Printers, add a second printer. Local Port, Select the port just created (i.e. USB001) Note: Some versions of Windows have a checkbox to autodetect, if it's there, uncheck this.
  3. Manufacturer: Generic, Printer: Generic / Text Only
  4. Use the driver that is currently installed
  5. Give the printer a name that differentiates it from the one already created, i.e. Zebra TTP8200 Raw.
  6. Do not share
  7. Do not print test page, Finish

Now with your raw printing application, use the newly created printer.

P.S. These instructions are also available here, with screenshots, as part of a Java open source raw printing tutorial. The project provides tutorials for other platforms (Ubuntu, OS X) as well.

http://qzindustries.com/TutorialRawWin

-Tres

Solution 3

If the USB printer is available as a COM port, you can just write to the COM port. Like this, from the DOS prompt:

dir > com1

The former example will output the results of the dir command to the printer.

Or, here is another example:

copy file.txt com1

The former example will output the contents of file.txt to the printer.

Outputting properly formatted ZPL data will be harder than just plain text. I have gotten this to work from Linux using Ruby (and Epson/ESC commands), however.

Share:
55,322
Al Bennett
Author by

Al Bennett

Updated on January 26, 2020

Comments

  • Al Bennett
    Al Bennett over 4 years

    I'm experimenting with a Zebra TTP8200 thermal printer. For my application I need to print plotter type traces continuously until the user hits a stop button. I've had a play with the ZPL language and I can successfully generate bitmap data and dump out my bitmap a line (or few lines) at a time by outputting the ZPL as raw data.

    I'm using some Microsoft demo code to output the raw data to the printer and this works great, bar one issue: the spooler. It turns out that every time I output some data using the MS rawprn.exe code it is actually spooled as a print job and then transmitted to the printer. This takes up to 10 seconds to get through the spooler, obviously too slow. Disabling spooling in the driver doesn't help, it just means that the program hangs while the job is passed through the spooler and printing completes.

    Is there a way to bypass the spooler and output data straight to this USB printer? My research so far hasn't turned up anything likely looking in the Windows API. Ideally, I'd like to be able to use the printer like it was a serial printer - open the port and shove data in.

    Many thanks in advance for any hints!

  • Al Bennett
    Al Bennett about 13 years
    Hi Teddy, thanks for this, it doesn't appear as a COM port. If it did this would all be as easy as you suggest! It would be nice if there was an alternate driver that just made it a fake serial interface.
  • Teddy
    Teddy about 13 years
    Mfg website says printer supports parallel printing. You can write to LPT1 port just the same as a COM port. Just replace COM1 with LPT1 in my examples.
  • Al Bennett
    Al Bennett about 13 years
    Hi Teddy, I'm pretty sure the version I have is USB only (but I'll check when I'm back in front of it tomorrow). For this application we'll actually be using the PC's parallel port for another device and I'd rather not be using an add-in PCI card - that just seems too 1990s!
  • Lynn Crumbling
    Lynn Crumbling over 9 years
    -1: No code in this answer, and a dead link :( Add some code, and I'll change the -1 to a +1!
  • tresf
    tresf almost 8 years
    If you manually share the USB printer on the network, it can be mapped to a local LPT port and used in the same fashion as this solution.
  • phuclv
    phuclv over 7 years
    @QZSupport you don't have to share it on the network, because you can access it via the loopback card like in your linked question, or use \\localhost
  • tresf
    tresf over 7 years
    @LưuVĩnhPhúc You're saying you can access a network printer via 127.0.0.1 without sharing it? I don't believe it.
  • phuclv
    phuclv over 7 years
    @QZSupport your linked question say the reverse. So you didn't even read what you posted. "If you don't have a network adapter, you can install the Microsoft loopback adapter, which emulates a network adapter, create a printer share on your machine, then use the "net use" command to print to the printer share.". Obviously you don't need to share it to everyone, just yourself
  • tresf
    tresf over 7 years
    @LưuVĩnhPhúc Are you nitpicking the definition of "network"? I'm not sure that's worth the argument. A 127.0.0.1-only virtual adapter network is a valid IPv4 network. "Sharing it on the network" is the same steps, so telling people they don't have to do that step is wrong. Arguing semantics helps no one. Microsoft specifically says "Only users on the network...". This is a UNC network share and telling people they don't have to share it on the network just obfuscates the facts in lieu of what one may consider a "physical network", which is irrelevant.
  • Nick Chan Abdullah
    Nick Chan Abdullah about 5 years
    hi Al, I got to try this soon. ya know, Argox printer utility program can send eltron/datamax commands directly to usb without any printer driver. so I was wondering about that.
  • Richard Chambers
    Richard Chambers over 2 years
    Anyone who finds this answer helpful may also find the discussion in post stackoverflow.com/questions/40775369/… helpful as well.