Empower Your Printing with Custom Print Processors

Empower Your Printing with Custom Print Processors

n this world of emerging technologies and modern, ultra-small, and super-capable electronic devices, there is still a place of honor for printers. People haven’t completely switched to soft copies. There are many reasons to look at a printed document instead of scrolling through it on your PC. For example, some technical writers insist on having hard copies of old documentation on which to make comments while working on a new version.

Whatever the reason, people still print often and, in many cases, they may want to coordinate printing activities or alter print jobs on-the-fly, etc. There are many ways to take control of the printing process. Using Custom Print Processors is one of them.

The Print Job Life Cycle
Figure 1 depicts the major stages through which a print job passes?starting from the Windows application request, to print, and ending with the actual print-out.

Users initiate the process by sending their documents to the printer. Normally, in the Windows corporate environment, the application sends the print job to the print server, which shares the network printer device. The Windows Print Spooler service receives the print request and spools the job to disk. Next, the Print Processor associated with the printer device is called to translate the print job to device language. Afterwards, the Print Monitor performs the additional task of directing the translated data stream to the appropriate printer port (COM, LPT, etc.) using the CreateFile, WriteFile, ReadFile, and DeviceIOControl Win32 API functions. Finally, you receive your hard copy on the device.

Figure 1. An Overview of Printing Stages: The image shows the stages through which a print job passes.

What Is the Print Processor?
As its name suggests, the Print Processor processes jobs that are spooled to the printer. You may view the Print Processor as a layer between a Windows application and the printer driver. This layer translates the job stream from the supported data type (such as NT EMF, RAW, TEXT, etc.) to a specific printer language with the help of the appropriate driver. Every printer is assigned a Print Processor in charge of handling the print jobs spooled to it. Print Processors are also responsible for handling any requests to stop, pause, or continue printing.

In fact, the Print Processor is a user-mode DLL, which runs in the context of the Windows Print Spooler service. Microsoft supplies code samples as part of the Driver Development Kit (DDK)?even though it is not a driver. Because of this, it’s important to remember that, while you don’t need driver development knowledge to implement your own Print Processor, you will need C/C++ and Win 32 programming experience to understand the essence of the code.

Exported Functions
It’s time to take a look at the interface between the Windows Print Spooler and the Print Processor DLL. Table 1 lists and explains the roles of the six functions you’ll need to export.

Function Name



Retrieves data types that are supported by the Print Processor


Gets capabilities of the Print Processor with respect to a specified data type


Opens the print processor and obtains its handle at the beginning of the printing session


This function converts the spooled Print Job to the Printer Device language


Sends control requests to the print processor to pause/stop/continue printing


Closes the print processor when the printing session is over

Table 1. Exported Print Processor Functions

Frontiers: What Can You Do with Print Processors?
Since a stop at the Print Processor is mandatory for every print job, developers have almost unlimited control over printing activities. As the previous section states, every Print Processor must inform the Windows Spooler of the supported data types. Each type is handled differently and the level of control varies. For example, the RAW data type represents the data in the Printer Driver-specific format. If you want to examine the contents of a RAW print job, you need to write a device-specific handler. This narrows your range of supported printers.

Fortunately, most Windows Applications spool print jobs in NT EMF (the device-independent Extended Meta File format). EMF is documented and makes it easier to look at the actual data being printed. The Print Processor translates the EMF job to the device language by playing EMF records on the printer device context with the help of the Windows GDI API. When developing your own Print Processor, you are free to call upon GDI functions to alter a print job.

Print Processors handle all print jobs using the security context of the NT/2000/2003 domain account that issued the print job. This means you can identify a user from within your code and enforce any security policy you wish. For example, you can block the job for specific users if their Exchange mail box is too large. Print Processors are extremely useful for security, because simply defining the Access Control List for the printer cannot set these kinds of complex permissions.

What You Need to Start Coding
First, you’ll need to configure your development environment:

  • Install Microsoft Visual Studio 7
  • Install Windows DDK 2000 (or higher)

Both products are supplied with a subscription to the MSDN CD/DVD package.

When developing Print Processor DLLs, you can use either the DDK build tool from the command line or an IDE, such as VS6 or VS7. The sample provided with this article was developed with Visual Studio 7.

How to Implement Your Own Print Processor
Using the Microsoft DDK sample for your Print Processor makes it really easy to develop your own. The code in this article is based on the genprint DDK and the sample, written in C, was converted to C++ to benefit from object-oriented model strengths.

The sample Print Processor which comes with this article contains an additional separator page for each print job with customizable content: a 1000×1000 bitmap image located at C:SeparatorPPSeparator.bmp. You may replace it with a picture of your choice and the Print Processor will use it.

Take some time to go over the source files and understand their roles. Table 2 lists these files with their appropriate descriptions and types:

  • DDK: This means the file was taken from the Microsoft DDK sample.
  • DDK Modified: This means the file was taken from the Microsoft DDK sample and modified.
  • New: This means the file is an entirely new creation.

File Name




Exports definition files

DDK (renamed from winprint.def)


This file implements functions that should be exported by every Print Processor

DDK (renamed from genprint.c)

local.h; local.cpp

Debugging output functions

DDK (local.cpp is renamed from local.c)


Contains support routines for the Print Processor

DDK (renamed from support.c)


Spool memory management functions

DDK (renamed from util.c)


Handles the RAW data type printing

DDK (renamed from raw.c)


Handles the TEXT data type printing

DDK (renamed from text.c)


Handles sending formfeed to a printer

DDK (renamed from parsparm.c)


Handles the NT EMF data type printing; this is where the sample code involves addding a separator page

DDK Modified (renamed from emf.c)


Defines the abstract base class (CEMFJobModifier) for EMF job modifications


JobSeparator.h; JobSeparator.cpp

Defines and implements the job modifier class, which adds the separator page; derives from the CEMFJobModifier class


Table 2. The table lists the sample source files and provides a short description and the origin of each file.

Because the DDK sample was originally written in pure C and converted to C++, all the files have the CPP extensions instead of the original C extensions.

Run the new code during the execution of the PrintDocumentOnPrintProcessor exported function. Listing 1 shows the standard implementation of the function (taken from SeparatorPP.cpp). As you can see, the function routes the execution to the appropriate handler, depending on the print job data type. For the sake of simplicity, this sample adds a separator only to NT EMF print jobs. This means you’ll be concentrating on the PrintEMFJob function located in the emf.cpp source file. The function accepts two parameters:

  • PPRINTPROCESSORDATA: The pointer to the job data.
  • LPWSTR: The document name.

Listing 2 shows the code for the PrintEMFJob function. Only the relevant code appears (for the full code, refer to the source code). The bold lines of code show the modifications that were made to support the separator page functionality. One of the first stages in printing the NT EMF job is to obtain the device context for the printer device (hPrinterDC):

hSpoolHandle = GdiGetSpoolFileHandle(pData->pPrinterName,                                             pDevmode,                                             pDocumentName);if (hSpoolHandle){   hPrinterDC = GdiGetDC(hSpoolHandle);}

The code then uses this handle to add the separator page. Right after the PrintEMFJob function starts the document printing, create an instance of the CJobSeparator class and modify the print job:

DocInfo.cbSize = sizeof(DOCINFOW);DocInfo.lpszDocName  = pData->pDocument;DocInfo.lpszOutput   = pData->pOutputFile;DocInfo.lpszDatatype = NULL;if (!GdiStartDocEMF(hSpoolHandle, &DocInfo)) goto CleanUp;bStartDoc = TRUE;////////////////////////////////////////////////////////////// Adding Separator PageCJobSeparator JobSeparator(hPrinterDC);JobSeparator.ModifyPrintout();// End of Adding Separator Page////////////////////////////////////////////////////////////

The actual job modification happens in the ModifyPrintout function, which is implemented by the CJobSeparator class (see Listing 3).

The StartPage API starts a new page, where you draw the bitmap loaded from the C:SeparatorPPSeparator.bmp file. ModifyPrintout uses the helper method RetrieveBitmapInfo to build the BITMAPINFO structure prior to stretching the bitmap to the printer device context. Finally, EndPage finishes printing the page.

After adding the separator page, PrintEMFJob completes the print job by playing EMF records?with the help of the GDI API?and translating them into the specific printer device language.

Installing the Print Processor
The following actions will install your Print Processor on the Print Server or your local computer (of course, all these operations can be automated when creating the installer for your application):

  • Copy the compiled DLL to the Print Processors folder:
  • Create a new Registry Key Separator under the existing [HKLMSYSTEMCurrentControlSetControlPrintEnvironmentsWindows NT x86Print Processors] key.
  • Add the driver REG_SZ variable to the newly created Separator key with the Print Processor DLL name as its value (in this case, Driver=SeparatorPP.dll).
  • Restart the Windows Spooler Service using the Services applet or with the following commands:
     net stop spooler net start spooler

    For your convenience, the registry script file is included in this article’s source code (SeparatorPP.reg).

  • From the Control Panel, go to Printers and Faxes and open the Properties window for the printer device.
  • Go to the Advanced tab (see Figure 2) and click on the Print Processor… button. From the displayed list, choose your print processor (as shown in Figure 3). Leave the default data type as RAW (this setting is relevant only when application doesn’t specify a data type for a print job; this is extremely rare; most NT systems will spool print jobs in EMF format).

Figure 2. Printer Properties: Go to the Advanced tab and click on the Print Processor button.
Figure 3. Selecting Print Processor: Choose your print processor from the displayed list.

Author’s Note: Remember to always be very careful when modifying the registry, because it may cause your machine to become inoperable.

Debug Your Print Processor
As already mentioned, Print Processors run in the context of the Windows Print Spooler Service. In order to debug your application, attach to the spoolsv.exe process and place breakpoint in the code. This is really easy if you?re using Microsoft Visual Studio 7 (see Figure 4).

Figure 4. Debugging: Attach to the spoolsv.exe process and place breakpoint in the code.

Don’t forget to copy the PDB file to the Print Processors folder when you wish to open a debug session.

From Hard Work to Hard Copies
Now, try printing a document to ensure that your Print Processor is functioning properly. Print it to the device which was associated with the Print Processor as explained in the “Installing Print Processor” section. The Separator.bmp file should be the first page printed with every hard copy.

Seek Out and Explore New Applications
This relatively simple technique allows you to take control of the printing activities on specific printer devices. You can use the code provided with this article or the code from the DDK to empower your printers, add missing functionality, alter print jobs, etc. Just remember not to waste too much paper testing your code?use virtual printers, print to file, or at least feed the printer with recycled paper.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist