This site uses advanced css techniques
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.
C> net stop spooler C> net start spooler
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).
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.
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):
Only two are actual DLL symbol entry points:
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.
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; }
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:
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:
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;
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 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.
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:
|
DrvPrinterEvent REQ'D |
This function handles certain events that might require action
by the driver. Only the INITIALIZE event must be supported:
|
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. |
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
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.
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:
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 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 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:
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.
The Windows DDK provides a functional Print Processor sample in the %WINDDK%\src\print\genprint\ directory, and each one exports relatively few entry points.
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). |
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.
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).
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 |
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.
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: