Does this site look plain?

This site uses advanced css techniques

[Printers & Faxes logo]

This is a work in progress and has not been publicly released yet. Lots of things are yet incomplete.

Most users see the Windows printing system as merely a list of printers offered by the "Printers & Faxes" control-panel applet, but developers who have to actually write software to participate in this system face a bewildering array of components:

The documentation for these parts is scattered throughout MSDN in separate areas, and it's never been terribly easy to get a big-picture view of how they all work. Though finding out how to write a print driver has been straightforward, digging into the other areas of print monitors is a lot more confusing.

We've been working with printing system components since the mid 1990s and are only now getting a real understanding of some of these niche areas, and our research has prompted us to create this Tech Tip. We wish somebody else would have written it years ago.

Our main audience is the software developer who has to work with the printing system, perhaps to create a print driver. We'll make numerous references to the Microsoft DDK: we're looking at the 2003 DDK as of January 2005 as we write this.

Note that we only covering the 2k/xp/2k3-based printing system: Win95/98/ME are completely ignored, and we'll only touch on the substantial NT4-specific differences briefly.

Overview of the printing components

Print Spooler
This is the the general name for the entire Windows printing system, which mainly involves the SPOOLSV.EXE executable. This is a system service that's typically launched at boot time, and it manages all the other components.
The spooler houses the RPC server component that listens for local and remote print requests, as well as the Router component that hands jobs off to a Print Provider (see below) once received.
The spooler can be started and stopped from the Services control panel applet, or from the command line with:
C> net stop spooler

C> net start spooler
Print Router
Once the spooler has received a print job via the RPC server, it has to decide what to do with it: the Print Router locates a Print Provider that's able to handle it.
TODO: How does the router select a job? I don't see any "print provider" references in the registry for the printers defined there. I get the impression that the router runs through each Print Provider until it finds one that can handle the job, but it's not clear how it makes this query.
The Print Router does not appear to be a customizable subsystem, nor one that can be replaced.
Print Provider
This is the heart of the system: it does what most of us think of as "print spooling", but in a modular way that allows for more than one component doing the same thing. It's virtually never worth considering writing one's own print provider.
One gets a feel for the complexity of a print provider by listing some of the functions provided (this list omits quite a few housekeeping functions):
  • Add/Delete/Enumerate Printer
  • Add/Delete/Enumerate Printer Driver
  • Add/Delete/Enumerate Form
  • Add/Delete/Enumerate Print Processor
  • Add/Delete/Enumerate Print Monitor
  • Manage PrinterChangeNotification
  • Create/Delete/Cancel/Enumerate Print Job
  • Add/Delete/Configure/Enumerate Printer Port
  • Perform XcvData operations

There are two broad types of Print Providers: local and partial. A local print provider is a final destination for print jobs, so it's got the most work to do. It's very difficult to imagine a reason to write a custom local print provider.

A Partial Print Provider, on the other hand, is a proxy that merely routes jobs from the local machine to a remote print server for disposition. There is one, of course, for routing to a Windows print server (where the Local Print Provider handles it), as well as those for Novell or IPP printing. One could imagine writing a new Partial Print Provider for a different network printing system (say, routing to an IBM mainframe printserver).

Print Driver
These are probably the most familiar component, as well as the most common to be created independently. A print driver converts a set of drawing/imaging instructions into a form required by a specific kind of printer. Drivers are usually defined by the type of printer they support ("HP LaserJet 4200 PCL Driver") or the output they produce ("PostScript Driver").
Print Drivers have two main parts:
  • The Graphics DLL, which converts the print instructions from the application to the final output form. This always runs in the context of the Print Spooler.
  • The Configuration DLL, which provides the GUI dialog for setting device and document properties. This runs in the context of the user's application or from the Printers and Faxes control panel applet.
Most printer drivers have additional housekeeping files, but these are minor players. We'll touch on how some of them work later.
Font Drivers
These are specialized DLLs that serve up fonts.
TODO: I've seen references to font drivers, but it's not clear exactly where they fit. The impression I get is that they are separate entities from straight GDI print/video drivers, and I saw a fleeting but un-noted reference to a location in the registry where they were defined. Adobe Type Manager seems to be one of these.
Print Processor
A Print Processor is a supervisor for the handling of one local print job. It receives the print data from an input source, examines the type, and runs whatever conversions are required to produce data in a form that a printer can directly accept.
Sometimes these conversions are essentially no-ops - the data is already in RAW form - but other data types require running through the Print Driver to convert the drawing instructions into that same RAW form. There are several Print Processor Data Types, RAW, EMF, TEXT and PSCRIPT. Other types are possible as well.
Once the data has been converted, it's handed off to a Print Monitor for delivery to the target device.
Print Monitor
This is a generic term for both Language and Port Monitors, each of which accepts output from a Print Processor (which typically gets it from a Print Driver) and routes it to the final output place. "Port Monitors" are visible to the administrator, "Language Monitors" are much less so.
These two objects form the end of the chain in the printing process, and though all installed printers are associated with a Port Monitor, some also have a Language Processor as well. These are linked together in a kind of pipeline, as illustrated here:
Print Monitors
Port Monitor / Printer Ports
A Port Monitor supports Printer Ports, and these are the familiar objects one uses when adding a new printer. Printer Ports include LPT1: (parallel devices), COM1: (serial devices), USB devices, and TCP/IP connections to HP JetDirect devices. Even print-to-file (FILE:) is implemented as a Printer Port.
Printer Ports are supposed to be conduits only: they ought not be aware of any meaning of the data they're handling or of the type of printer they're connected to. This applies in both directions: to RAW printer codes being sent to the device, and status information read from the device.
In practice, however, many custom Port Monitors do in fact combine the "conduit to device" and "understand the data and status". This is a poor practice for reasons we'll touch on next.
Language Monitor
A Language Monitor - which doesn't have anything to do with "Language" in the "English or German" sense - is used to interact with an output device that is capable of bidirectional command and status reporting. Unlike Port Monitors, which are physical communications channels with no particular knowledge of the kind of printer on the other end, Language Monitors are always aware of this.
The best example of a Language Monitor is PJLMON, which understands HP's Printer Job Language. When a print job starts, the Language Monitor can query the printer with @PJL INFO STATUS, read the response, and take an appropriate action (start printing, display an "Out of Paper" message, etc.).
By separating the printer-specific conversation from the channel-specific handling, it's easy to support a specialized device with one Language Monitor even when (say) USB support is added to the device.
It may not be possible to always maintain this strict separation: if the device has peculiar hardware specifications (say, a nonstandard parallel port handshake not supported by the LPT1: Port Monitor), then there may be no choice but to support a combined Language/Port monitor.

Print Drivers

These are the most visible elements of the printing system, providing graphics support for a particular kind of printer (the Graphics DLL) as well as the configuration dialog boxes (the GUI DLL) that users see every day. Print Drivers are the most commonly customized part of the printing system.

The "input" to the Graphics DLL is not a data stream, but instead a sequence of function calls from the GDI layer requesting operations like "start page", "set font", "stroke a line", or "copy some image bits". The driver converts these to the proper codes required to make this happen on the output device and then call call the WritePrinter() function to pass data to the Port Monitor for delivery to the printer.

[Flow of GDI/printer data]

The Print Processor is responsible for translating the GDI calls from the application (or a spooled metafile) into the DDI as implemented by the driver. Some functions are always required, while others are optional: the GDI layer will handle them if omitted.

There is a very long list of entry points (some required, some optional):

DrvCompletePDEV REQ'D
DrvDisablePDEV REQ'D
DrvDisableSurface REQ'D
DrvEnableDriver REQ'D
DrvEnablePDEV REQ'D
DrvEnableSurface REQ'D
DrvEndDoc REQ'D
DrvQueryDriverInfo REQ'D
DrvSendPage REQ'D
DrvStartDoc REQ'D
DrvStartPage REQ'D
DrvAlphaBlend
DrvBitBlt
DrvCopyBits
DrvCreateDeviceBitmap
DrvDeleteDeviceBitmap
DrvDestroyFont
DrvDisableDriver
DrvDitherColor
DrvDrawEscape
DrvEscape
DrvFillPath
DrvFontManagement
DrvFree
DrvGetGlyphMode
DrvGetTrueTypeFile
DrvGradientFill
DrvLineTo
DrvLoadFontFile
DrvNextBand
DrvPlgBlt
DrvQueryAdvanceWidths
DrvQueryDeviceSupport
DrvQueryFont
DrvQueryFontCaps
DrvQueryFontData
DrvQueryFontFile
DrvQueryFontTree
DrvQueryGlyphAttrs
DrvQueryPerBandInfo
DrvQueryTrueTypeOutline
DrvQueryTrueTypeTable
DrvRealizeBrush
DrvResetDevice
DrvResetPDEV
DrvStartBanding
DrvStretchBlt
DrvStretchBltROP
DrvStrokeAndFillPath
DrvStrokePath
DrvTextOut
DrvTransparentBlt
DrvUnloadFontFile

Only two are actual DLL symbol entry points:

DrvQueryDriverInfo
This allows the GDI layer to ask questions about what the driver can do, and as of this writing there is only one value supported: DRVQUERY_USERMODE. If this query returns TRUE, this is a user-mode print driver, otherwise it's Windows-2000-style kernel driver.
Technically, this function is optional, but if it's missing the DLL is presumed to be a kernel-mode driver. These are discouraged on all platforms and outright forbidden on others (Server 2003 SP1). Because the only way to be a "proper" user-mode driver is to provide this entry point, we're marking it REQ'D.
DrvEnableDriver
This populates a table of index/function pointers that define which entry points this driver implements. The GDI runs through this table and uses the driver's version if available, else it falls back to a generic implementation. The

All the rest of the functions are table entry points, not DLL entry points, and they fill out the rest of this list. Many of the functions perform obvious jobs: start start page, set font, render text, draw line, fill polygon, etc.

How driver entry points are defined
static DRVFN gadrvfn[] =
{
    { INDEX_DrvEnablePDEV,       (PFN) DrvEnablePDEV      },
    { INDEX_DrvDisablePDEV,      (PFN) DrvDisablePDEV     },
    { INDEX_DrvEnableSurface,    (PFN) DrvEnableSurface   },
    { INDEX_DrvDisableSurface,   (PFN) DrvDisableSurface  },
    { INDEX_DrvBitBlt,           (PFN) DrvBitBlt          },
    { INDEX_DrvStretchBlt,       (PFN) DrvStretchBlt      },
    { INDEX_DrvCopyBits,         (PFN) DrvCopyBits        },
    ...
};

BOOL DrvEnableDriver(ULONG iEngineVersion, ULONG cb, PDRVENABLEDATA pded)
{
    ...
    pded->iDriverVersion = DDI_DRIVER_VERSION;
    pded->c      = sizeof(gadrvfn) / sizeof(DRVFN);
    pded->pdrvfn = gadrvfn;
    ...
    return true;
}

The DEVMODE

A key data structure involved in printing is known as the DEVMODE, and it can be best thought of as the Document Properties. When a user chooses to print a document in landscape on Legal sized paper with "draft" color mode, these instructions are all maintained in the DEVMODE.

There is no standard definition of a DEVMODE; each driver defines the bits in its own way, and they are not portable between unrelated drivers (even though the two drivers might support the same features). There can be, however, multiple versions of a DEVMODE from the same driver if client and server versions are different. Drivers are required to provide a function that reconciles version-skew differences when this circumstance arises.

Generally speaking, all features exposed to the user in the the DocumentProperties dialog box maps more or less directly to some fields in the devmode. The user can set these default parameters via the Printing Properties dialog box, available in one of two ways from the Printers & Faxes control-panel applet:

[Default Document Properties]

These are two ways of getting to the same dialog boxes, and in both cases the result is a DEVMODE which is stored in binary form in the Registry. The DEVMODE's format is defined in source code, and though vendors virtually never document theirs, a fragment of a real DEVMODE production looks like this:

A real DEVMODE
typedef struct {
        ...

        /* WATERMARKS - text to appear on every page. */
        bool        bWmarkEnabled;
        DOUBLE      dfWmarkRotate;
        DWORD       dwWmarkFontSize;
        COLORREF    crWmarkTextColor;
        WCHAR       szWmarkText[WATERMARK_TEXT_SIZE];

        /* STAPLING: only ON or OFF */
        bool        bStaple;

        ...
} DEVMODE;

Surfaces and drawing

The GDI draws upon a canvas known as a "Surface", and they participate in most of the DDI calls (via the SURFOBJ data type). Most drivers use a GDI-managed surface, which is little more than a bitmap representing the markable space, and this relieves the driver of the need to implement most of the functions.

For instance, DrvLineTo draws a one-pixel-wide line from point A to point B, and if the only thing the driver would do is set the proper bits in the bitmap, it's much easier (and usually more efficient) to "punt" on this facility by omitting it from the DrvEnableDriver table. The GDI notices that the driver hasn't provided an implementation and simply provides a reasonable default one.

This works at a more fine-grained level too: the DrvStrokePath entry point is used for advanced path drawing, and if the driver doesn't support a particularly complex path, GDI will break it down into smaller, simpler pieces.

The result of one page's worth of drawing is a full bitmap that represents the whole document to be printed; this bitmap can be delivered to the printer in some kind of encoded raster form.

The other type of surface is a device-managed surface, in which case the driver is responsible for doing everything. The most obvious example is in a display driver: the driver is probably writing directly to Video RAM. The GDI has no knowledge of how to talk to video hardware, so the driver must implement almost all of the entry points. It also has opportunities for optimization and hardware accelleration that GDI simply couldn't know about.

Using a GDI-managed surface means the output is essentially in raster form, and most printer drivers are of this form. But not all: PostScript drivers typically use a device-managed surface because they are able to translate the high-level nature of the DDI calls into device language. When the driver receives a DrvLineTo call, for instnace, it may well map it into the PostScript lineto operator, avoiding all bitmap translations and taking best advantage of the maximum device resolution. One can imagine a true PCL or plotter driver that does the same thing.

The GUI DLL

The graphics DLL is clearly the business end of a print driver, but all drivers require a user interface as well for configuration and setup. This job is often (but not always) found in a separate DLL that provides for the full user interface.

Printer Driver Graphics DLL API Functions
Function Description
DevQueryPrintEx REQ'D This function determines if a specified print job is compatible with the printer's current configuration and can therefore be printed. If the job cannot be printed, it returns FALSE and sets an error string in the caller-provided DEVQUERYPRINT_INFO structure.
DrvConvertDevMode REQ'D This function converts a printer's DEVMODEW structure from one version to another. In a client/server environment, there can be multiple versions of a DEVMODE in play: newer versions of a DEVMODE usually have more members than older ones, and this function reconciles those differences.
DrvDeviceCapabilities REQ'D This function returns information about what the printer can do, and it's largely mapped from the user-level GetDeviceCaps Win32 API call. An application (or the spooler) provides a token indicating the capability in question. These includes queries about available media, types of input and output bins, color support, resolutions, and many other capabilities.
DrvDevicePropertySheets This defines (but does not display) a set of "property sheets" that are later displayed in the configuration dialog box for performing "Device Settings".
DrvDocumentEvent This function handles certain events which transpire in the course of printing a document, but it's run in the context of the user-mode GDI client. We're not sure under what circumstances this might prove useful.
DrvDocumentPropertySheets REQ'D A parallel to DrvDevicePropertySheets, this creates property sheets for "Document Properties" to be display in the print properties dialog box.
DrvDriverEvent This gives the driver a "hook" to installation and delete operations involving the driver itself. These events include:
  • DRIVER_EVENT_DELETE
  • DRIVER_EVENT_INITIALIZE
DrvPrinterEvent REQ'D This function handles certain events that might require action by the driver. Only the INITIALIZE event must be supported:
  • PRINTER_EVENT_INITIALIZE REQ'D
  • PRINTER_EVENT_ADD_CONNECTION
  • PRINTER_EVENT_ATTRIBUTES_CHANGED
  • PRINTER_EVENT_CACHE_DELETE
  • PRINTER_EVENT_CACHE_REFRESH
  • PRINTER_EVENT_DELETE_CONNECTION
  • PRINTER_EVENT_DELETE
DrvQueryColorProfile This queries the ICC color profiles for the device.
DrvQueryJobAttributes This function allows a printer interface DLL to specify support for such capabilities as printing multiple document pages on a physical page ("N-up" printing), printing multiple copies of each page, collating pages, and printing pages in reverse order.
DrvSplDeviceCaps
Server2003 & later
This function (available in Windows Server 2003 and later) responds to queries about a device's capabilities much like DrvDeviceCapabilities does.

TODO: what's the difference?

DrvUpgradePrinter This is a hook to allow a driver to update registry settings when a new version of a driver is installed.

The Microsoft OEM Drivers

A simple print driver can "punt" on most of the hard work of marking a surface by allowing the GDI to do it, but there is nevertheless a fair amount of housekeeping involved to support the same "draw on a bitmap" functionality that would be common to most print drivers. This is on top of the work require

To simplify the task of producing a driver for new hardware, Microsoft provides two frameworkds

Print Providers

These are the real workhorses, and clearly the most elaborate of the replaceable components in the printing system. Fortunately, it's almost never necessary to write a new one, because remote printing systems are not nearly as numerous as printers or communications channels.

When looking at the API that has to be implemented - which is very substantial - the first thing that strikes one when dealing with this area is the inconsistent spelling: we see both Provider and Providor (though the API function names universally use the latter). We'll generally use "Provider" unless the API requires otherwise.

Curiously, though there are API functions AddPrintProvidor() and DeletePrintProvidor(), there is no EnumPrintProvidors(); instead one must use EnumPrinters() with a special set of parameters:

On our XP/SP2 system, EnumPrinters() reveals three providers. The enumeration interface doesn't show the DLL names (and they don't seem to be found, but we managed to locate them separately. The don't appear in the "obvious" place in the registry either, which we found odd.

Print Providers
Name Comment Description Flags DLL
Windows NT Local Print Providor Locally connected Printers Windows NT Local Printers PRINTER_ENUM_CONTAINER
PRINTER_ENUM_ICON1
localspl.dll
Windows NT Remote Printers Remote Printers Microsoft Windows Network PRINTER_ENUM_EXPAND
PRINTER_ENUM_CONTAINER
PRINTER_ENUM_ICON1
Win32spl.dll
Windows NT Internet Provider Internet URL Printers Windows NT Internet Printing PRINTER_ENUM_CONTAINER
PRINTER_ENUM_ICON1
inetpp.dll

Looking in more detail at these providers:

Windows NT Local Print Providor
This is the main workhorse provider, being much larger than the others. Being the local provider, it's responsible for managing all printers reached by local printer ports. This includes the obvious hardware connections (serial, parallel, USB), as well as network connections such as HP JetDirect.
Windows NT Remote Printers
This provider allows for management of printers on remote print servers: requests made to this provider are redirected to the remote server, which hands the request off to the local provider there.
Printers of this type look like \\SERVER\PRINTER3 in the usual "Network Neighborhood" style, as well as those found in Active Directory.
Windows NT Internet Provider
This is the HTTP print provider, which sends jobs to URLs. Microsoft supports this under IIS with special printing URLs, and this Microsoft documentation describes it how it's supported.
It's more broadly supported by IPP - Internet Printing Protocol - which is covered by RFC 2639 (and others).

It's important to note that these are not the only possible Print Providers, they are merely the only ones installed on the current system. Though the local Print Provider is sufficient to manage all attached printers, one might create a new Print Provider to reroute jobs to a remote server not otherwise supported. For instance, nwprovau.dll routes print jobs to Netware print servers, and it seems like any other kind of remote printing server could use a Print Provider.

But it's a lot of work. Microsoft provides a sample in %WINDDK%\src\print\pp\, but it's just a skeleton that shows the entry points but has no actual implementation. Partial Print Providers, as the name implies, need not implement every entry point, but it's still a nontrivial task.

The main (and only, besides DllMain) entry point is the InitializePrintProvidor function, which returns PRINTPROVIDOR structure. This defines a very large number of function pointers, many of which are required for all print providers, and some which are only required for local providers.

The functions required are documented by Microsoft, but we've found the list incomplete as shown by the members of the PRINTPROVIDOR structure. This list has been largely lifted from the Microsoft documentation (with our own additional commentary).

Since the Print Spooler largely proxies the Win32 printer functions by calling the Print Provider implementations, we'll include hotlinks to the "regular" Win32 function pages; in many cases, there is no specific DDK documentation for what the function should implement.

TODO: confirm the API list with the MS page, then move this long API list to another file

Print Provider API Functions
PRINT QUEUE MANAGEMENT FUNCTIONS
AddPrinter Adds a print queue to the list of those managed by the print provider, and associates a print processor with the print queue.
AddPrinterConnection Creates a connection to the specified print queue.
ClosePrinter REQ'D Disables caller access to a specified print queue
DeletePrinter Deletes a print queue from the list of those managed by the print provider.
DeletePrinterConnection Removes a connection to the specified print queue.
EnumPrinters REQ'D Enumerates the list of print queues currently managed by the print provider.
GetPrinter REQ'D Returns current parameter values for a specified print queue.
ResetPrinter Modifies a print queue's data type or DEVMODEW structure.
SetPrinter REQ'D Sets parameters for a specified print queue.
FindFirstPrinterChangeNotification Returns a handle to a wait object that the caller can use to wait for specified printer events
FindClosePrinterChangeNotification Disables printer change notifications that were enabled by FindFirstPrinterChangeNotification
RefreshPrinterChangeNotification Called by router if client calls FindNextPrinterChangeNotification with the PRINTER_NOTIFY_OPTIONS_REFRESH flag set.
WaitForPrinterChange obsolete
PRINTER DRIVER MANAGEMENT
AddPrinterDriver Adds a specified printer's driver files to a specified server.
AddPrinterDriverEx Same as AddPrinterDriver, with additional parameters.
DeletePrinterDriver Deletes access to a specified printer's driver files, on a specified server.
DeletePrinterDriverEx Same as DeletePrinterDriver, with additional parameters.
EnumPrinterDrivers Returns a list of printer drivers that have been added to a specified server by calling AddPrinterDriver or AddPrinterDriverEx.
GetPrinterDriver Returns information about a printer driver, which the caller can then pass to AddPrinterDriver. (The returned information is typically obtained from an INF file.)
GetPrinterDriverEx Same as GetPrinterDriver, with additional parameters.
GetPrinterDriverDirectory Returns the name of the server's printer driver directory.
PRINT JOB CREATION
AbortPrinter REQ'D Atteclassmpts to delete the current job from the specified print queue.
AddJob REQ'D Returns a job identifier and spool file path. The caller uses CreateFile and WriteFile to send data to the spool file.
ScheduleJob REQ'D Informs the provider that a specified job can be scheduled. The job is specified by a job identifier previously returned by AddJob.
ReadPrinter Obtains status information from a bidirectional printer.
StartPagePrinter Prepares the print provider to receive a print job page.
EndPagePrinter Performs page completion operations.
WritePrinter REQ'D Receives a portion of the print job's data stream.
StartDocPrinter REQ'D Prepares the print provider to begin spooling a print job.
EndDocPrinter REQ'D Performs job completion operations.
PRINT JOB SCHEDULING
EnumJobs REQ'D Returns a list of scheduled print jobs. Note that jobs may appear other than by use of the AddJob function; a remote foreign print server is also accepting jobs via its own native mechanisms.
GetJob REQ'D Returns job parameters.
SetJob REQ'D Cancels, pauses, resumes, or restarts a print job, or sets job parameters.
FORMS MANAGEMENT
AddForm Adds a specified form to the list of those available for a specified printer.
DeleteForm Removes a specified form from the list of those available for a specified printer.
EnumForms Returns a list of forms available for a specified printer.
GetForm Returns characteristics of a specified form.
SetForm Modifies characteristics of a specified form.
PRINT PROCESSOR MANAGEMENT
AddPrintProcessor Installs a print processor on the specified server and adds it to the list of those that the print provider can call.
DeletePrintProcessor Deletes a print processor from the list of those that the print provider can call.
EnumPrintProcessorDatatypes Returns a list of the data types supported by the print processors that are callable by the print provider.
EnumPrintProcessors Returns the list of print processors that the print provider can call.
GetPrintProcessorDirectory Returns the directory path in which print processor files must be stored.
PRINT MONITOR MANAGEMENT FUNCTIONS
AddMonitor Adds a print monitor to the list of those that the print provider can call.
DeleteMonitor Deletes a print monitor from the list of those that the print provider can call.
EnumMonitors Returns the list of print monitors that the print provider can call.
PORT MANAGEMENT FUNCTIONS
AddPort Adds a printer port to the list of those available, typically by calling the specified port monitor's AddPortUI function. Note that this is not required: adding a port may require facilities not supported by any remote operation.
AddPortEx Same as AddPort, with additional parameters.
ConfigurePort REQ'D Configures a printer port, typically by calling the specified port monitor's ConfigurePortUI function.
DeletePort REQ'D Deletes a printer port from the list of those available, typically by calling the specified port monitor's DeletePortUI function.
EnumPorts REQ'D Returns a list of available printer ports.
SetPort Sets parameters for a specified printer port.
REGISTRY MANAGEMENT
DeletePrinterData Deletes the value currently assigned to a specified value name, under the specified printer's PrinterDriverData key.
DeletePrinterDataEx Same as DeletePrinterData, with additional parameters.
DeletePrinterKey Deletes a specified key and its subkeys, if they are currently stored in the registry under the specified printer's PrinterDriverData key.
EnumPrinterData Returns each of the value names and currently assigned values that are stored in the registry under the specified printer's PrinterDriverData key.
EnumPrinterDataEx Same as EnumPrinterData, with additional parameters.
EnumPrinterKey Returns a list of subkeys currently contained in the registry under a specified key name.
GetPrinterData Returns the value currently assigned to a specified value name, which is stored in the registry under the specified printer's PrinterDriverData key.
GetPrinterDataEx Same as GetPrinterData, with additional parameters.
SetPrinterData Stores a specified value name and value in the registry, under the specified printer's PrinterDriverData key.
SetPrinterDataEx Same as SetPrinterData, with additional parameters.
OTHER FUNCTIONS
XcvData Provides a communication path between a port monitor UI DLL and a port monitor server DLL.
FUNCTIONS NOT DOCUMENTED
AddPrinterEx  
AddDriverCatalog  
AddPerMachineConnection  
CloseSpoolFileHandle  
ClusterSplClose  
ClusterSplIsAlive  
ClusterSplOpen  
CommitSpoolData  
CreatePrinterIC  
DeletePerMachineConnection  
DeletePrinterIC  
DriverUnloadComplete  
EnumPerMachineConnections  
FlushPrinter  
GetSpoolFileInfo  
OpenPrinterEx  
OpenPrinter  
PlayGdiScriptOnPrinterIC  
PrinterMessageBox Appears to be obsolete.
SeekPrinter  
SendRecvBidiData  
ShutDown  
SplReadPrinter  

Print Processors

Print Processors are virtually never seen directly by the user, though they can be maintained by clicking the little-used "Print Processor" button on a printer's Advanced tab in Properties:

[Print Processor dialog box]

WinPrint is the standard Microsoft-provided Print Processor, and additional processors are often found on a system. They can be queried with the EnumPrintProcessors() API function. Furthermore, each Print Processor has a list of data types associated with it, and queryable via the EnumPrintProcessorDatatypes() API call:

C> winprinfo --enumprocessors

Print processor dir: C:\WINDOWS\System32\spool\PRTPROCS\W32X86

[0] Processor = "ModiPrint"
    Datatype  = "RAW"
    Datatype  = "NT EMF 1.006"
    Datatype  = "NT EMF 1.007"
    Datatype  = "NT EMF 1.008"
    Datatype  = "TEXT"

[1] Processor = "HPPRN05"
    Datatype  = "RAW"
    Datatype  = "RAW [FF appended]"
    Datatype  = "RAW [FF auto]"
    Datatype  = "NT EMF 1.003"
    Datatype  = "NT EMF 1.006"
    Datatype  = "NT EMF 1.007"
    Datatype  = "NT EMF 1.008"
    Datatype  = "TEXT"

[2] Processor = "WinPrint"
    Datatype  = "RAW"
    Datatype  = "RAW [FF appended]"
    Datatype  = "RAW [FF auto]"
    Datatype  = "NT EMF 1.003"
    Datatype  = "NT EMF 1.006"
    Datatype  = "NT EMF 1.007"
    Datatype  = "NT EMF 1.008"
    Datatype  = "TEXT"

These Print Processors are defined in the registry, with a different set for each environment (the type of processor hosting the operating system). Most computers use the Windows NT x86 environment:

HKEY_LOCAL_MACHINE\
    SYSTEM\
    CurrentControlSet\
    Control\
    Print\
    Environments\
    Windows NT x86\
    Print Processors\
    name

The Microsoft documentation discusses the various data types, and we'll summarize them here. Note that though the utility of each type seems apparent, we've never seen any reason to change the settings in the Printer Properties nor can imagine any good result from doing so.

RAW
This is data that's ready to send to the printer without further processing, and it's usually the output of a Print Driver. This is virtually always the output of a Print Processor regardless of what the input type was. Wnen a Print Processor receives RAW data on input, it does not invoke the associated Print Driver's graphics DLL.
EMF
Enhanced MetaFile is a format that essentially encapsulates GDI instructions, and it's done in a device-independent way. For a given image, EMF data is typically smaller than the corresponding RAW data, and it's used when the generating application does not have the proper print driver locally: it generates EMF and routes it to the remote print server, which does have the proper Print Driver to convert to RAW.
Applications can also spool print jobs to EMF rather than send to a Print Driver directly: this sends the graphics instructions to a local spooler file which can be completed quickly, allowing the spooler to render the job asynchronously.
There are apparently multiple versions of EMF, though we have no idea what the differences are.
TEXT
This is just what it looks like: ordinary ASCII data. When a Print Processor receives a TEXT job, it routes it through GDI using the device's default font, and the resulting RAW data is routed to the output. We presume that TEXT inputs might result from legacy applications that are accustomed to writing to a physical printer directly (perhaps via an LPT1: redirection).
PSCRIPT1
This is PostScript data, which is handled by the sfmpsprt.dll Print Processor. This doesn't seem to ship by default on Windows XP, but it was found on an SBS 2003 server. It's a very small DLL, and it's clear from peeking inside that it's intended for printing to Mac systems.
We are reasonably sure that this print processor does not actually render PostScript to some lower-level form, so it's likely that it's intended to just be a passthrough Print Processor. In this case, the output - PostScript - is considered "RAW" from the point of view of the target device.

The Windows DDK provides a functional Print Processor sample in the %WINDDK%\src\print\genprint\ directory, and each one exports relatively few entry points.

Print Processor API Functions
Function Definition
DllMain REQ'D This is the usual entry point called in all DLLs when they are loaded into the calling process's address space. A Print Provider has no special requirements on this front.
OpenPrintProcessor REQ'D This prepares a Print Processor DLL to handle one print job. The name of the printer associated with this job is passed, along with a PRINTPROCESSOROPENDATA structure that defines parameters of the job. This includes a JOB_INFO_2 structure that contains the input data type; this allows the Print Processor to know how to interpret the data it's seeing.

The return value is a HANDLE that must be passed to the remaining API calls used during this print job. Internal to the Print Processor, the HANDLE is usually a pointer to an object allocated from memory, but from the point of view of the caller (the Print Provider), it's entirely opaque.

The print-job data should not touched by this function.

PrintDocumentOnPrintProcessor REQ'D Given the HANDLE from the OpenPrintProcessor() call, plus the name of a file containing the data, convert that data to the proper output format. For RAW input, it's little more than a simple ReadPrinter() / WritePrinter() loop, but the other types generally require processing through GDI. The result is sent back to the spooler with WritePrinter().

This API call is blocking with respect to the Print Provider: long jobs take a long time.

ControlPrintProcessor REQ'D The spooler can asynchronously interrupt a job while processing by calling this API function. It's intended to be used while PrintDocumentOnPrintProcessor() is running (from a different thread, of course), and the parameter includes a token that requests JOB_CONTROL_CANCEL, JOB_CONTROL_PAUSE, or JOB_CONTROL_RESUME.

It's not defined just how the Print Processor accomplishes this, though using a waitable Event object seems like a good candidate.

ClosePrintProcessor REQ'D This call finishes up a job running through the Print Processor. It must free up all resources associated with the HANDLE that was allocated and returned from OpenPrintProcessor().
EnumPrintProcessorDatatypes REQ'D This returns a list of data-type strings: it's called by the local print provider at initialization, as well as by the user-mode API call of the same name.
GetPrintProcessorCapabilities This optional function allows a Print Processor to announce its capabilities to the print provider, or to an application via the GetPrinterData() call. A string representing the data type is passed, as well as a PRINTPROCESSOR_CAPS_1 object that's filled in. This defines a few capabilities, such as the number of pages per output sheet, the order of print (first or last page first), and the number of copies).

Print Monitors

This is a confusing area, and we'll try to make it a bit clearer than the Microsoft documentation.

Print Monitors -- generally -- receive device-formatted data from a Print Processor and are responsible for getting the data to the final device. This is provide in one (Port Monitor) or two (Port+Language Monitor) parts.

A Port Monitor is a communications conduit only: it's given some data, it passes it to the destination without knowing anything about the data it's handling. A standard LPT: port, for instance, has no idea if it's passing binary, PostScript, or other data to the attached parallel port. All port monitors pass data to a printer, but some devices permit bidirectional communications and can receive data from the printer.

A Language Monitor, on the other hand, knows how to have a two-way conversation with the target device for the purposes of status and setup, but it relies on the Port Monitor to provide the bidirectional physical communications channel.

Some devices, such as HP LaserJets, support both a printer graphics language (PCL, PostScript), but also a job-control language, and it's possible to ask the status of the printer: are you ready? what media is loaded? are there any error conditions? This status information is unrelated to the graphic data or to the communications channel: it can be asked over parallel, serial, USB, or network.

[Print Monitor Data Flow]

At the start of a job, the Language Monitor typically has a converation with the printer to check status. If all is well, it starts to pass graphics data received from the Print Driver onto the Port Monitor, which sends it to the printer itself. The Print Driver is unaware that this status query is going on, and the Port Monitor can't tell "status query" from "graphics data".

If, during printing, the output device sends back some kind of error message ("out of paper"), it's read (but not understood) by the Port Monitor, which passes it unmodified to the Language Monitor. The Language Monitor parses the message and can take proper action.

Both kinds of Print Monitors require a Server DLL that's called by the spooler to perform the supported functions, and Port Monitors additionally provide a User Interface DLL for performing configuration tasks in the context of the Printers and Faxes Control Panel applet. It's possible to put both parts (Server and UI) into the same DLL.

As is the case with most of these other printing system components, these DLLs export one or two initialization functions, each of which returns a structure with the full list of functions that are actually supported.

All Print Monitor DLLs must export a InitializePrintMonitor2() function, which returns a MONITOR2 structure with the function pointers.

Note: the documentation for this API function suggests that it's for use with "clustered print servers", but it's the correct function to call for all Print Monitors. The InitializePrintMonitor() function is obsolete (it was supported in NT4).

Port/Language Monitor Functions
Function Description Port Monitor Language Monitor
EnumPorts This populates a caller-provided array of in PORT_INFO_1 or PORT_INFO_2 structures that describe all the ports supported by this monitor. REQ'D  
OpenPort This opens and creates a HANDLE to a Port Monitor, where it may be used to send one or more print jobs to a target device. REQ'D  
OpenPortEx This opens and creates a HANDLE to a Language Monitor, where it may be used to send one more print jobs to a target device through an intermediate Port Monitor. This Port Monitor is reached via functions found inside the MONITOR2 structure provided as a parameter.   REQ'D
StartDocPort This sets up the given Print Monitor to print one job on the given port. This is paired with the EndDocPort() function to bracket the print job. REQ'D REQ'D
WritePort This function receives data from the previous stage in the printing process and Writes it to the next: the Language Monitor (if any) writes data to the Port Monitor, and the Port Monitor writes to the printer. REQ'D REQ'D
ReadPort This reads data from the device and passes it back to the caller. The Port Monitor reads directly from the printer, and the Language Monitor reads from the Port Monitor. This is typically used for status and error monitoring. REQ'D REQ'D
EndDocPort This ends the print job that was started by StartDocPort REQ'D REQ'D
ClosePort This closes the given port, releasing all resources associated with it. REQ'D REQ'D
AddPort This NT4.0 function is obsolete.
The XcvData() call with the "AddPort" operation string should be used instead.
   
AddPortEx This NT4.0 function is obsolete.
The XcvData() call with the "AddPort" operation string should be used instead.
   
ConfigurePort This NT4.0 function is obsolete.
The XcvData() call with the "ConfigurePort" operation string should be used instead.
   
DeletePort This NT4.0 function is obsolete.
The XcvData() call with the "DeletePort" operation string should be used instead.
   
GetPrinterDataFromPort This function obtains status information from a bidirectional printer and returns it to the caller. It's much more elaborate than merely reading some raw data, and there are differences in behavior between Port and Language monitors. Optional Optional
SetPortTimeOuts This allows the spooler to set Port communications timeouts as defined by the COMMTIMEOUTS structure. Optional  
XcvOpenPort This opens an XcvData channel to allow for remote port management operations, such as adding, deleting, or configuring a port,and the returned HANDLE is passed to XcvDataPort(). REQ'D  
XcvDataPort This uses a HANDLE obtained from XcvOpenPort() along with an operation string with parameters, and performs specific functions inside a port monitor. These strings include "AddPort", "ConfigurePort", and "DeletePort"; this general mechanism replaces the API functions with those names. Additional custom operation strings may be support as well by a port monitor. REQ'D  
XcvClosePort This accepts a HANDLE obtained from XcvDataOpen() and closes it, releasing any resources allocated. REQ'D  
Shutdown This prepares a print monitor for deletion

TODO: Is this also used during normal spooler shutdown?

TODO: This is documented as "required" by the link in this link, but this page says it's optional.

Optional  
SendRecvBidiDataFromPort This function supports bidirectional communication between an application and a printer or print server, and it may be implemented by either a Port Monitor or a Language Monitor. Optional
XP only
Optional
XP only

User versus kernel mode

Virtually everything about the printing system runs in user mode, the exception being the lowest-level I/O drivers (say, the physical I/O ports, the USB subsystem, or the TCP/IP network stack). But this has not always been the case for Print Drivers.

Up to NT 3.51, Print Drivers were exclusively user mode entities: DLLs were loaded into the Graphics subsystem. These drivers allowed the full range of other user-mode programs and were relatively easy to work with. These were Version 1 drivers. TODO: is this true?

In NT4, graphics drivers -- which include video and print drivers -- moved from user space to kernel space. Video drivers generally have much higher performance requirements than print drivers, and moving them into kernel saved some nontrivial amount of context switches. These were Version 2 drivers.

One downside is that many of the facilities that were easy to use in user mode were unavailble or highly constrained in kernel mode. "Threads" and "floating point math" both come to mind, not to mention the increased difficulty of debugging kernel-mode components. This restricted environment made it nearly impossible to convert some kinds of drivers from user mode to kernel mode.

The other downside is that system stability suffered. When a user-mode program crashes, it doesn't typically take the entire operating system down with it, but kernel-mode drivers do. This sharp distinction is reduced somewhat because a crashed user-mode driver can still take down the graphics subsystem, and this is nearly the same as dropping the whole OS.

Due to the difficulties encountered by kernel-mode print drivers (both regarding development and stability issues), Microsoft moved print drivers back to user mode in Windows 2000. These are Version 3 drivers, and were highly similar to the NT 3.51 architecture.

Perhaps unwilling to again face the wrath of print-driver writers, the NT4 kernel-mode drivers were still supported going forward, though Windows Server 2003 SP1 has dropped them; no more kernel mode print drivers. Fortunately, converting a kernel-mode print driver to user mode is much easier than going the other direction.

TODO: How is V1 diff from V3?

Note that this user/kernel separation only applies to the graphics DLL; the User Interface DLL always runs in user mode.

Resources

Microsoft has spread information about the printing system in quite a few different places, and it doesn't seem terribly well coordinated. We've found some areas that sit at the top of trees holding promise and list them here:

Stuff I need to talk about eventually