Does this site look plain?

This site uses advanced css techniques

There is no easy way to write a Windows printer driver, but some ways are easier than others.

Table of Contents

Though some printers may well require a custom, from-scratch monolithic driver, the great majority of output devices are perfectly well supported by Microsoft's stock Unidriver or PostScript drivers with a bit of customization.

These customizations are provided the driver author who implements a set of standard COM (Component Object Model) interfaces in (at least) a pair of DLLs: these amount to user-provided hooks into the mainline driver.

Unfortunately, the dozen or so interfaces are organized — and named! — in a way that's been nontrivial to understand, and it's created a great deal of confusion for this driver author.

So we spent some time sorting it all out, and present this Tech Tip to describe the Windows Printing System COM API.

So what is COM?

We had never used COM before this project, but found that it's a kind of object-oriented interface without using C++. The idea is that a module (usually a DLL) can offer a set of services to clients in a way that clients of all types can understand.

This will be the briefest possible introduction to COM: there are undoubtedly far better resources available on the internet.

When a customization DLL is installed into the printing system, the Microsoft drivers ask: "Do you support interface <NAME>?". If yes, your DLL will return a pointer to a structure of a predefined type, and whose members are pointers to methods supporting that interface. If not supported, it returns E_NOINTERFACE.

This process repeats for each interface (and version of interface) that the printer driver expects an add-in DLL to support. If the user-provided DLL doesn't provide some minimum of required support, it rejects the DLL.

Curiously, QueryInterface() is not performed with the name of the interface, but by GUID (in the form of an IID - Interface ID). These IDs are defined in the prcomoem.h header file in the Windows DDK, and one such interface is shown here:

%WINDDK%\inc\wnet\prcomoem.h
...
//
// Interface ID for IPrintOemDriverUI interface
//
// {92B05D50-78BC-11d1-9480-00A0C90640B8}
//

DEFINE_GUID(IID_IPrintOemDriverUI, 0x92b05d50, 0x78bc, 0x11d1,\
        0x94, 0x80, 0x0, 0xa0, 0xc9, 0x6, 0x40, 0xb8);
...

When one wishes to implement an interface, one uses C++ to inherit from the DDK-provided base class. Since the base class is entirely virtual, the derived class must name all the members. Choosing an interface at random:

//  ICustomPrinterPS
//
//  Interface for PostScript OEM sample rendering module
//
class ICustomPrinterPS : public IPrintOemPS2
{
public:
    // *** IUnknown methods ***
    STDMETHOD(QueryInterface) (THIS_ REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG,AddRef)  (THIS);
    STDMETHOD_(ULONG,Release) (THIS);

    // Method for publishing Driver interface.
    STDMETHOD(PublishDriverInterface)(THIS_ IUnknown *pIUnknown);

    // Method for OEM to specify DDI hook out
    STDMETHOD(EnableDriver)  (THIS_ DWORD           DriverVersion,
                                    DWORD           cbSize,
                                    PDRVENABLEDATA  pded);

    // Method to notify OEM plugin that it is no longer required
    STDMETHOD(DisableDriver) (THIS);
    ...

Now that the interface structure is defined, the key method QueryInterface() — required by all COM objects — replies positively to any query about an interface we support (note that IUnknown is a COM housekeeping interface):

HRESULT __stdcall ICustomPrinterPS::QueryInterface(const IID& iid, void** ppv)
{
    if (iid == IID_IUnknown)
    {
        *ppv = static_cast(this);
    }
    else if (iid == IID_IPrintOemPS2)
    {
        *ppv = static_cast(this);
    }
    else if (iid == IID_IPrintOemPS)
    {
        *ppv = static_cast(this);
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    reinterpret_cast(*ppv)->AddRef();
    return S_OK;
}

Microsoft's print driver code will query for IPrintOemPS and IPrintOemPS2 — both of which are supported — and the mechanism extends nicely to some future IPrintOemPS3: failure to support that hypothetical interface doesn't inherently break the code or cause a DLL linkage error.

The caller has the ability to only use the levels of interfaces actually supported.

General organization

Now that the general idea of how COM interfaces work have been covered, it's time to look at the ones revolving around the customization subsystem. There are at least 12 interfaces involved, and they are named and organized in na way that's not easy to understand.

It's best to start understanding the three broad classes of interfaces and how they work together before digging into the details:

PostScript versus Unidriver interfaces
This is clearly the most obvious distinction — it's hard to imagine any single driver implementing both PostScript and raster capabilities, though we suppose it's possible.
The PostScript interfaces are only useful to PostScript printers, while the Unidriver interfaces apply to everything else. Many major vendors support Unidriver-based print drivers, including even Hewlett Packard for its LaserJet printers.
If you're not sure, you need the Unidriver interfaces and can ignore the other.
Rendering versus UI interfaces
Your hooks will separately extend both the rendering DLL (for working with the output printer data stream) and the User Interface DLL (for dialog box support). You'll generally implement one interface in each class, though that interface may include sub-versions too.
For instance, if you implement IPrintOemUI2 to extend the UI, you'll also implement IPrintOemUI (the latter is a subset of the former).
In older versions of Windows, the rendering DLLs ran (or could run) in kernel mode, while the User Interface DLLs ran in user mode, but user mode rendering has been supported since Windows 2000, and Vista only supports user mode DLLs.
User-provided versus Microsoft-provided interfaces
Some interfaces are meant to be implemented by your code and called by the Microsoft driver framework, and this is where all the work lay ahead for you.
But your code can also call some Microsoft-provided interfaces to perform common functions or query the internal state of the driver. These are mostly optional, but available nevertheless.
You do not have to implement these interfaces!
For instance, your rendering DLLs can call DrvWriteSpoolBuf inside the Microsoft-provided IPrintOemDriverUni (Unidriver) and IPrintOemDriverPS (PostScript) interfaces to route printer data to the spooler where it is passed on to the final output device.
These are calls back into the Microsoft drivers to support your DLLs.

The sheer number of interfaces and the various categories and classes can be quite daunting, so we've organized them here in a table that shows how they all shake out. Most driver developers need only consider the interfaces associated with the technology they are supporting, ignoring the "other", and we split them between interfaces you must implement yourself, and Microsoft-implemented interfaces your code can use.

Note that a few of the UI interfaces are common to both technologies, and even the technology-specific interfaces have a common look and feel, allowing knowledge of one to help implementing the other.

Per-technology COM interfaces
Interface How implemented Module Description
Unidriver
IPrintOemUni
» IPrintOemUni2 — XP++
» IPrintOemUni3 — Vista++
You implement Rendering Add your hooks to the rendering DLL
IPrintOemUI
» IPrintOemUI2 — XP++
User Interface Add your hooks to the UI DLLs
IPrintOemDriverUni Microsoft provided Rendering Microsoft-provided rendering helper methods
IPrintOemDriverUI User Interface Microsoft-provided UI helper methods
PostScript
IPrintOemPS
» IPrintOemPS2 — XP++
You implement Rendering Add your hooks to the rendering DLL
IPrintOemUI
» IPrintOemUI2— XP++
User Interface Add your hooks to the UI DLLs
IPrintOemDriverPS Microsoft provided Rendering Microsoft-provided rendering helper methods
IPrintCorePS2 — XP++ Microsoft-provided rendering helper methods
IPrintOemDriverUI
» IPrintCoreUI2— XP++
User Interface Microsoft-provided UI helper methods

Table Legend:

» extends previous interface
interfaces common to both technologies
extends IPrintOemDriverUI, but for PostScript only
XP++ XP/2003/Vista and later
Vista++ Vista and later

We've grouped interfaces which are subsets/supersets of each other: where each subsequent Keep in mind that interfaces often are extended by newer versions, and this means that the IPrint....2 interface often adds just two or three methods to the base IPrint.... interface in question. We've grouped all related interfaces into the same box in the first column.

Rendering DLL

In any print driver, the rendering is where all the action is: this is where the graphics instructions from the user are turned into printer-specific data bits and routed to the output device.

Both the Unidriver and PostScript drivers respond to more or less the same input commands, but the output is quite different.

User-provided UI COM Interfaces

User-provided UI DLLs must implement at least one of these two interfaces, which are called by the driver core to manage the customized parts of the printer user interface dialogs.

IUnknown IPrintOemUI IPrintOemUI2 Method Description
YES YES YES QueryInterface Common to ALL COM objects
YES YES YES AddRef Common to ALL COM objects
YES YES YES Release Common to ALL COM objects
NO YES YES IPrintOemUI::CommonUIProp Allows a user interface plug-in to modify an existing printer property sheet page or document property sheet page.
NO YES YES CommonUIProp Allows a user interface plug-in to specify customized device capabilities.
NO YES YES DevicePropertySheets Allows a user interface plug-in to add a new page to a printer device's printer property sheet.
NO YES YES DevMode Performs operations on a user interface plug-in's private DEVMODEW members.
NO YES YES DevQueryPrintEx Allows a user interface plug-in to help determine if a print job is printable.
NO YES YES DocumentPropertySheets Allows a user interface plug-in to add a new page to a printer device's document property sheet.
NO YES YES DriverEvent Called by the print spooler when processing driver-specific events that might require action by the printer driver.
NO YES YES FontInstallerDlgProc Replaces the Unidrv font installer's user interface.
NO YES REQ'D GetInfo Returns a user interface plug-in's identification information.
NO YES YES PrinterEvent Allows a user interface plug-in to process printer events.
NO YES REQ'D PublishDriverInterface Supplies a pointer to the Unidrv or Pscript5 driver's IPrintOemDriverUI COM helper Interface.
NO YES YES QueryColorProfile Allows a printer interface DLL to specify an ICC profile to use for color management.
NO YES YES UpdateExternalFonts Allows a printer interface DLL to update a printer's Unidrv font format files.
NO YES YES UpgradePrinter Allows a user interface plug-in to upgrade device option values that are stored in the registry.
NO   YES DocumentEvent Allows a UI plug-in to replace the core driver UI module's default implementation of the DrvDocumentEvent DDI.
NO   YES HideStandardUI Allows a Pscript5 UI plug-in to specify whether the standard property sheets should be displayed or hidden.
NO   YES QueryJobAttributes Allows a UI plug-in to postprocess the core driver's results after a call to the DrvQueryJobAttributes DDI.

Microsoft-provided UI Helper Interfaces

Customization UI DLLs can call upon two interfaces exposed by the Microsoft print drivers. The simplest of the two, IPrintOemDriverUI provides limited access to the driver's settings, while IPrintCoreUI2 gives extensive access to the PScript5 PPD internals.

IPrint
OemDriverUI
IPrint
CoreUI2
Method Description
YES YES DrvGetDriverSetting Allows a user interface plug-in to obtain the current status of printer features and other internal information. This includes GPD access from Unidrv or PPD access from PScript5, though the feature support is quite limited.
YES YES DrvUpdateUISetting Allows a user interface plug-in to notify the driver of a modified user interface option. This allows the main driver to know that settings must be refreshed from the DEVMODE or registry. This is required, for instance, if a custom tab modifies the same data that the stock property pages read from; they won't otherwise know that the data must be redisplayed.
YES YES DrvUpgradeRegistrySetting Allows a user interface plug-in to update device settings stored in the registry. This should not really be used any longer: the settings described should be in a PPD or GPD file.
  YES EnumConstrainedOptions Determines which options of a feature are constrained.
  YES EnumFeatures Enumerates a printer's available features.
  YES EnumOptions Enumerates the available options of a specific feature.
  YES GetFeatureAttribute Retrieves the feature attribute list or the value of a specific feature attribute.
  YES GetGlobalAttribute Retrieves the global attribute list or the value of a specific global attribute.
  YES GetOptionAttribute Retrieves the option attribute list or the value of a specific option attribute.
  YES GetOptions Retrieves the driver's current feature settings in the format of a list of feature/option keyword pairs.
  YES QuerySimulationSupport Retrieves a spooler simulation capability structure, which indicates the kinds of simulation the spooler supports.
  YES SetOptions Sets the driver's feature settings.
  YES WhyConstrained Determines why the specified feature/option selection is constrained.

Unidrv and Pscript Interfaces: Quick Reference

This printer OEM customization system has a tremendous number of interface and methods, and it's hard enough to keep them straight when only the interfaces and methods are considered.

What makes it worse is Microsoft's complete inability to index their own content on their own website with respect to these documents. Links are broken more often than not, and we have lost more time to this than we care to think about.

So, we've manually tracked them all down and are summarizing them here - all the links should be good. We'll consider additional kinds of organization later.

Methods for User Interface Plug-Ins
IPrintOemUI / IPrintOemUI2

OEM-implemented interfaces for providing user interface behavior.

IPrintOemDriverUI

Microsoft-Provided helper interfaces to support the UI customization DLLs

IPrintCoreUI2

Microsoft-Provided helper interfaces to support the UI customization DLLs

Methods for Rendering Plug-Ins
IPrintOemPS / IPrintOemPS2

OEM-provided rendering plugin for PostScript

IPrintOemDriverPS

PScript5-provided helper functions supporting the rendering plugins

IPrintOemUni / IPrintOemUni2 / IPrintOemUni3

OEM-provided rendering pluging for Unidriver

IPrintOemDriverUni

Unidriver-provided helper functions supporting the Unidriver customization DLLs

IPrintCorePS2

Unidriver-provided helper functions supporting the Unidriver customization DLLs (XP/2003 only)