This site uses advanced css techniques
There is no easy way to write a Windows printer driver, but some ways are easier than others.
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.
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:
... // // 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.
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:
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.
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.
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 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. |
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. |
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.
OEM-implemented interfaces for providing user interface behavior.
Microsoft-Provided helper interfaces to support the UI customization DLLs
Microsoft-Provided helper interfaces to support the UI customization DLLs
OEM-provided rendering plugin for PostScript
PScript5-provided helper functions supporting the rendering plugins
OEM-provided rendering pluging for Unidriver
Unidriver-provided helper functions supporting the Unidriver customization DLLs
Unidriver-provided helper functions supporting the Unidriver customization DLLs (XP/2003 only)