Does this site look plain?

This site uses advanced css techniques

kernel-to-user icon Microsoft's printing subsystem has been somewhat of a moving target for Win32 developers over the last ten years or so, by moving printer drivers from user mode, to kernel mode, and then back to user mode. For a time, both have been allowed, but Vista reportedly blocks all kernel mode drivers, so there's been quite a scramble to make that "last" conversion back to user mode.

Table of Contents

Most parts of the driver code will remain unchanged during this conversion, but some of the housekeeping and administrative sections do require modification, and we have found scant resources to help the developer tasked with this conversion.

Some of Microsoft's MSDN pages provide some guidance, and Google gives some additional clues, but we've seen no real centralized guide to the process. Several of us have independently burned multiple days on the same "obvious" issues — it's been very frustrating — and this prompted us to create this Tech Tip.

Caveats and Conventions

We've been working with print drivers on and off since Windows NT 3.51 but we do not have a comprehensive knowledge of what's required for this conversion: we only wrote these notes while tripping over things during conversion of an existing driver. Other drivers may have other issues.

We're creating a guide with advice, not an authoritative reference.

These notes apply only to full Win32 systems: NT4, Windows 2000, XP, 2003, and beyond. We have no experience whatsoever with Windows 95, 98 or ME and would be surprised if any of this advice applied to those systems.

We're also targetting only monolithic drivers, which are more or less standalone, and do not apply to the PostScript or UniDriver OEM extension DLLs. We have a great deal of experience with PostScript OEM extensions in user mode, but don't know how much of this applies to a kernel-to-user conversion.

All of our development was done using the Windows 2003 SP1 DDK, and testing done on Windows XP.

Since video drivers use more or less the same GDI subsystem as do print drivers, so some of this information may apply, though we're not sure anybody is converting video drivers to user mode.

When showing code snippets, we've adopted the convention of showing both user and kernel mode code with #ifdef using USERMODE_DRIVER:

#ifdef USERMODE_DRIVER
   user-mode code here
#else
   kernel-mode code being replaced
#endif

We're not proposing that the code actually contain both paths of the #ifdef — a user-mode driver probably won't need to revert — but it felt like the most effective method to convey the gist of the kernel-mode code being replaced.

Iconography

danger icon During the conversion process, some things which are wrong are obviously wrong, and the error messages or behavior makes it clear what must be done to remedy the concern.

But some area are not obvious, and more than one of us has spent days looking for the reason for this or that incorrect behavior. These are danger zones which are very easy to miss, and we've highlighted them with this icon.


fuzzy dice icon Finally, we're really fuzzy on quite a few aspects of the process, mainly because it's our first real commercial kernel to user conversion.

We don't fully understand all the parts of the existing kernel driver, so we're marking these sections with a fuzzy-dice icon to indicate less clarity than usual. Take these sections with a grain of salt.


Background and History of Driver Mode Changes

All Win32 print drivers have two parts: a user-interface part for configuration by the user, and a rendering part which does the actual graphics. The two parts are installed and work as a pair (usually including helper files, such as fonts or a PPD).

The UI component always runs in user mode — usually in the context of the process of the user requesting the printing — but the rendering part has been quite schizophrenic over the last ten years, moving back and forth between user mode and kernel mode.

In NT 3.51 — the first version that we wrote for — the rendering DLL ran in user mode, with full access to the full Win32 API that any process has.

In NT 4, Microsoft moved GDI (printer and video) drivers to kernel mode. This done for performance reasons: video drivers suffered when an output device required back and forth between kernel and user mode, and putting the rendering in the kernel produced a substantial speedup.

Though for video drivers this was particularly important, it's not clear that it was that important for printer drivers, but it was clear that the penalty for printer driver errors was a BSOD, not just a program crash.

Furthermore, this was an awful change for driver developers, because kernel mode did not provide the same environment as user mode:

Furthermore, kernel-mode drivers substantially reduced system stability for all but the most robust drivers, so in Windows 2000 the drivers optionally moved back to user mode, supporting both.

Kernel mode drivers have been more and more discouraged with each subsequent OS release: Windows 2000 Logo compliance requires user mode drivers (though an exception was made early in the program for proven reliable kernel mode drivers), and Windows 2003 does not allow them by default, requiring a Group Policy change to enable.

Windows Vista will not permit kernel mode drivers at all.

Rename the project and driver

Hello, My Name is driver3.dll User-mode and kernel-mode drivers are fundamentally different, and we think it's a bad idea to reuse the name of the kernel-mode DLL in the user-mode project.

For testing this probably doesn't matter much, especially if the testing machine has no fragments of the old driver around, but for deployment, we sense ugly collisions if the same filename is used for both flavors fo the driver.

Whether the project and directories are renamed is a matter of personal preference, but the names of the output DLLs really ought to be changed. This involves changing things in several places:

These are the bare minimum steps to change the name of the product as seen by the installers, and we recommend incrementing the product version information as defined in the resource files and the like.

Configure precompiled headers

Picture of Stopwatch Though not strictly required for the conversion, it's really helpful to enable precompiled header support for the project directories. This makes a big difference in compile time, and this kind of project typically requires a lot of build cycles.

This all works by precompiling all the definitions and typedefs and macros in header files up to a certain point, and it's used by all subsequent source files in that directory. Our approach has been to make a single file "precomp.h" as the first header file in every source file in the directory, and this file contains all the system includes:

precomp.h
/*
 * anchor for precompiled headers
 */
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <windef.h>
#include <winerror.h>
#include <wingdi.h>
#include <winddi.h>
#include <tchar.h>

#include <windows.h>
#include <winspool.h>
#include <stdio.h>

Our practice has long been to include only system header files — not headers defined by the project — and each project directory gets its own copy of this file. The files may be slightly different for each one, so this gives us the flexibility to tailor the file to the particular DLL or library being built.

Then, a few lines must be added to the SOURCES file to instruct the build system to use it:

PRECOMPILED_INCLUDE=precomp.h
PRECOMPILED_PCH=precomp.pch
PRECOMPILED_OBJ=precomp.obj
PRECOMPILED_CXX=1             — only if C++; else C assumed

This may provoke some reorganization of header file use in general, because this effectively requires that all source files start with the same set of header files, and they may not be currently designed for that. We feel that absent a really good reason, a consistent header-file arrangement is probably a plus for the project in general even if the benefits of precompiled headers are not considered.

Test this by running a build: hopefully it's much faster now.

Update the SOURCES file(s)

The driver DDK requires that all software be built from the command line using the build tool, and it uses the SOURCES and DIRS files to direct which directories and files are required, as well as many options for configuring the build process.

Converting a kernel-mode SOURCES file requires numerous changes, some of which are subtle and whose omission is hard to detect directly.

These changes must be made in not only the rendering DLL build directory, but in any common libraries that are shared between kernel and user mode. Many driver projects include common code in a separate library directory, building two versions of the same code (with separate UM and KM build subdirectories).

Replace -DKERNEL_MODE with -DUSERMODE_DRIVER
These two macros guide the set of declarations exposed by winddi.h (and perhaps a few others), and we must change one for the other.
C_DEFINES=$(C_DEFINES) -DUNICODE -DKERNEL_MODE -DUSERMODE_DRIVER
This is probably not strictly necessary for the UI component, but if the common library keys off USERMODE_DRIVER, there may be no harm including it everywhere.
danger icon Update target type
A kernel-mode driver has TARGETTYPE=GDI_DRIVER, but this must be changed to DYNLINK. Without this change, the output looks like a DLL, but it doesn't act or load like one.
TARGETTYPE=DYNLINK
Getting this wrong is silent but very painful
If the driver uses the MSVC C Runtime, it should include two additional lines in the SOURCES file to support it:
DLLENTRY=_DllMainCRTStartup
USE_MSVCRT=1
The first insures that the library internals get properly initialized at startup - DllMain() is called by the startup routine. If this is bypassed, all kinds of strange problems are likely to erupt.
Update TARGETLIBS
The TARGETLIBS macro contains a list of libraries required to link the output .DLL, and these must be updated to reflect the user-mode driver.
  • Update any references to kernel-mode versions of project-built libraries. If a common directory (as part of the driver project) contains a libarary shared between the UI and rendering parts, the reference should be change to user mode:
    TARGETLIBS=..\mylib\km/obj$(BUILD_ALT_DIR)/*/libdrv.lib
    
    Change ...\KM\... to ...\UM\... as required.
  • Remove libcntpr.lib - it provides C Runtime information for kernel-mode drivers, and is not needed here. Replacing the libcntpr.lib is usually done with a handful of user-mode libraries, the first of which is always required.
    $(DDK_LIB_PATH)\umpdddi.lib
    $(DDK_LIB_PATH)\user32.lib
    $(DDK_LIB_PATH)\kernel32.lib
    $(DDK_LIB_PATH)\winspool.lib
    
    Others may be required, but these are a good start.

Add DllMain() and DrvQueryDriverInfo() functions

Since we're now producing a regular DLL instead of a loadable kernel module, we must provide the usual DllMain() function required of all DLLs. In addition, one further function must be provided to tell the printing system that this is a user-mode driver, not a kernel mode one.

First, add DllMain(): we typically save a copy of the instance handle (into ghInstance, which ought to be global) in case we later need access to DLL resources. We also typically show a bit of startup information into the debugging log:

HINSTANCE ghInstance;

BOOL __stdcall
DllMain(HANDLE hModule, ULONG ulReason, PCONTEXT pContext )
{
    switch (ulReason)
    {
    case DLL_PROCESS_ATTACH:
        ghInstance = hModule;
        OutputDebugStringA("MyDrv DllMain() " __DATE__ " " __TIME__ "\n");
        break;

    case DLL_PROCESS_DETACH:
        break;
    }

    return TRUE;
}

The DrvQueryDriverInfo() function was introduced in the Windows 2000 DDK to allow the spooler to ask questions of the driver DLL, and the only query now is "Are you a user-mode driver?". Clearly Microsoft anticipates adding new queries, and it seems like a reasonable mechanism.

If this function returns FALSE in the pointed-to buffer, or if it is missing entirely, this driver is assumed to be kernel mode only, so we have no choice but to implement it. A suitable implementation might look like:

BOOL __stdcall
DrvQueryDriverInfo(
    DWORD   dwMode,
    PVOID   pBuffer,
    DWORD   cbBuf,
    PDWORD  pcbNeeded
    )
{
    switch (dwMode)
    {
    case DRVQUERY_USERMODE:
        *pcbNeeded = sizeof(DWORD);

        if (pBuffer == NULL || cbBuf < sizeof(DWORD))
        {
            SetLastError(ERROR_INSUFFICIENT_BUFFER);
            return FALSE;
        }

        *((PDWORD) pBuffer) = TRUE;
        return TRUE;

    default:
        OutputDebugStringA("DrvQueryDriverInfo - bad parameter\n");
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
}

Finally, since this is a DLL, we require the .DEF file to describe the exported functions, and there should be just four of them. This small file can be added to the project:

mydrv.def
; LIBRARY MYDRV

EXPORTS
        DllMain
        DrvQueryDriverInfo
        DrvEnableDriver
        DrvDisableDriver

The build tool requires that the name provided on the LIBRARY directive exactly match (including case) the TARGET name in the SOURCES file, so it's probably best to leave the LIBRARY line out of the .DEF file entirely and let the build system handle it automatically.

Omitting this file produces a linkage error during build time.

Update the rendering DLL's resource file

danger icon When trying to get our first-cut of our converted driver working, we found that the the AddPrinterDriver() function would always treat it as a kernel-mode driver.

After about three days, we ran across the reason: it seems that AddPrinterDriver() does not consider the version number in the DRIVER_INFO_n structure, but instead uses the version information in the .DLL.

Changing the second value on the FILEVERSION line from 2 to 3 got the system to recognize it as a user-mode driver:

mydrv.rc file
...
VS_VERSION_INFO VERSIONINFO
  FILEVERSION 0,3,0,0            // NOT 0,2,x,x
  PRODUCTVERSION 4,0,1381,1
  FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
  FILEFLAGS   VS_FF_DEBUG        // 0x1L
#else
  FILEFLAGS 0x0L
#endif
  FILEOS      VOS_NT_WINDOWS32   // 0x40004L
  FILETYPE    VFT_DLL            // 0x2L
  FILESUBTYPE VFT2_DRV_PRINTER   // 0x1L
  ...

Missing this burned a lot of time.

We're not sure if the UI DLL requires the same attention, but it seems prudent to make them all agree on the version.

Acquire access to the driver files

Because this is a user-mode driver, the .DLL file is automatically loaded into memory. The handle is passed to DllMain() and should be saved into a global variable, which can now be used directly (these examples assume that pdev points to the driver-defined PDEV structure).

#ifdef USERMODE_DRIVER
    pdev->hModule = ghInstance;
#else
    pdev->hModule = EngLoadModule(EngGetDriverName(hdev));

    if (pdev->hModule == NULL) {
        DBGERRMSG("EngLoadModule");
        FreePdevMemory(pdev);
        return NULL;
    }
#endif

It appears that releasing a loaded module is done with UnmapViewOfFile(), but for the rendering DLL itself this should not be called.

A printer can also have a "data file" associated with it, which could be be a PPD for a PostScript driver, or some other kind of file.

The documented API is EngGetPrinterDataFileName(), but this has always failed for us with error #127 (ERROR_PROC_NOT_FOUND). This function is imported by the DLL, it's exported by GDI32.DLL, and we can't find any reason why this shouldn't work.

But it doesn't, so we instead must use GetPrinterDriver() to fetch this data, as shown in the MSPLOT DDK sample.

LPWSTR pwszDataFile = NULL;

#ifdef USERMODE_DRIVER

    PDRIVER_INFO_2 pDriverInfo = NULL;
    DWORD       dwBytesNeeded;

    if (!GetPrinterDriver(hDriver, NULL, 2, NULL, 0, &dwBytesNeeded)
      && GetLastError() == ERROR_INSUFFICIENT_BUFFER
      && (pDriverInfo = (PDRIVER_INFO_2)LocalAlloc(LPTR, dwBytesNeeded))
      && GetPrinterDriver(hDriver, NULL, 2, (LPBYTE)pDriverInfo,
                         dwBytesNeeded, &dwBytesNeeded))
    {
        pwszDataFile = pDriverInfo->pDataFile;
    }
#else
    /* not supported in user mode - dunno why */
    pwszDataFile = EngGetPrinterDataFileName(hdev);
#endif

We've found no explanation why this is required.

Set the Engine Version

When the spooler calls the driver's DrvEnableDriver() function, it passes a flag indicating the operating system in use. This flag, defined in <winddi.h>, is one of:

Macro Operating system Hex Value
DDI_DRIVER_VERSION_NT4 Windows NT4 0x00020000
DDI_DRIVER_VERSION_SP3 Windows NT4 w/ SP3 0x00020003
DDI_DRIVER_VERSION_NT5 Windows 2000 0x00030000
DDI_DRIVER_VERSION_NT5_01 Windows XP 0x00030100
DDI_DRIVER_VERSION_NT5_01_SP1 Windows XP w/ SP1 0x00030101

These reflect not so much the operating system version, but the device driver interface version; each time Microsoft added or changed the API for talking to a printer driver, it incremented the numbers.

Looking at the hex values, one can see which changes were major and which were minor: it's no surprise that NT4 to Windows 2000 involved a major jump, but XP to XP/SP1 was very minor. Unfortunately, we don't know exactly which changes occurred in each version.

The driver makes use of this DDI version information in two ways:

  1. It insures that it's actually able to support that OS; a user-mode driver can't support NT4, for instance, so it would fail the call with ERROR_BAD_DRIVER_LEVEL.
  2. It can tailor the list of returned entry points to match what the operating system expects. If a driver implements a function added by a later version of Windows, an earlier version of Windows will reject the driver if it finds an implemented entry point that it doesn't understand.

We find it curious that Windows 2003 and Vista don't have any entries in this table, which suggests either that they actually haven't made any changes to the DDI, or that the DDK/MSDN haven't been updated yet. We'll see.

Microsoft's MSDN documentation on DRVENABLEDATA provides this table showing how the version support spans the various levels of OS:

Windows Version Value of iDriverVersion
Windows NT 4.0 iDriverVersion == DDI_DRIVER_VERSION_NT4
Windows NT 4.0 SP3 DDI_DRIVER_VERSION_NT4 <= iDriverVersion <= DDI_DRIVER_VERSION_SP3
Windows 2000 DDI_DRIVER_VERSION_NT4 <= iDriverVersion <= DDI_DRIVER_VERSION_NT5
Windows XP DDI_DRIVER_VERSION_NT4 <= iDriverVersion <= DDI_DRIVER_VERSION_NT5_01
Windows XP SP1 DDI_DRIVER_VERSION_NT4 <= iDriverVersion <= DDI_DRIVER_VERSION_NT5_01_SP1

We're Fuzzy We're not entirely sure what to make of this in the context of a user-mode driver. Though we think that a kernel-mode driver could support all operating systems from NT4 through XP/SP1, we believe that user-mode drivers must start with DDI_DRIVER_VERSION_NT5.

#ifdef USERMODE_DRIVER
#  define DDI_DRIVER_VERSION DDI_DRIVER_VERSION_NT5   // Probably?
#else
#  define DDI_DRIVER_VERSION DDI_DRIVER_VERSION_NT4
#endif

BOOL
DrvEnableDriver(...)
{
    if (iEngineVersion < DDI_DRIVER_VERSION ||
        cb < sizeof(DRVENABLEDATA))
    {
        DBGMSG(DBG_LEVEL_ERROR, "Invalid parameters.\n");
        SETLASTERROR(ERROR_BAD_DRIVER_LEVEL);
        return FALSE;
    }

Re-implement kernel-only DDI functions

The printing system provides a driver writer with a host of support functions, most of which are prefixed with Eng (for graphics Engine). The majority are supported in both modes, though some require adjustment in their new home. We'll touch on the ones we know about here.

Unsupported functions

Curiously, just four functions are defined as being outright unsupported in user mode, with no obvious replacement:

If the driver uses any of these functions, they must be removed. Though some (such as EngDebugBreak()) appear to be debugging functions which may be unnecessary in user mode — the normal debugging tools should work just fine — we're not sure how one replaces the need to get a list of Type 1 fonts.

Recommended conversions

The rest of the EngXXX functions are generally available, but Microsoft recommends (Here: "Choosing User Mode or Kernel Mode") that true user-mode functions be used instead of the Eng functions. Not all the conversions are obvious, and the guidance given should be used.

For instance, though one can replace the kernel-mode EngAcquireSemaphore() with the Win32 CreateSemaphore(), it's more efficient to replace all the semaphore calls with Critical Section objects.

Miscellaneous conversions

C Runtime library support has always been problematic in kernel mode, because it doesn't appear to have a "real" C runtime library, but user mode code has no such limitations. There are a host of familiar functions, mainly related to string processing, that are fully available in user mode.

Address Floating Point

3.14159 Kernel-mode does not support native floating point with the usual C data types, requiring instead a set of cumbersome macros and functions. This has proven to be a real annoyance to a driver author, and a substantial relief in moving back to user mode.

New user-mode code should just use the normal C floating-point data types and operations just like any other application, without any special arrangements, but code coming from kernel mode must address the cumbersome macros and functions.

There are three pseudo-types dealing with floating point which must be considered, and each has a different definition in each mode.

The central unit of quasi-floating point is the FLOATOBJ, defined in <winddi.h>, and in kernel mode this maps to a structure containing a pair of unsigned longwords (the same size as a C double data type), but in use mode it's instead mapped to FLOAT, which is defined as float (we find it curious that the "real" floating type is smaller than the fake one).

This table represents the various pseudo-floating point types used in kernel mode, and the real underlying types they map to in each mode:

Floating-point pseudo types
Pseudo-Type Kernel Mode User Mode Defined in
FLOATOBJ struct _FLOATOBJ {
  ULONG ul1;
  ULONG ul2;
};
FLOAT <winddi.h>
FLOATL DWORD FLOAT <winddi.h>
FLOAT undefined float <wtypes.h>

Though it's possible to completely rip out all of this and replace it with native types, it's probably not necessary, at least for the first conversion pass. Since the FLOATOBJ functions and macros all work in user mode, it might make sense to leave them alone until the driver is actually working properly, treating this as an optimization pass for later.

Delete UMPD Components

We're fuzzy on this The Windows NT4 DDK provided some limited user-mode hooks for the rendering side of a print driver, mainly to allow a small user mode component to intercept the output from the kernel-mode driver before it streams to the output device.

These UMPD (User Mode Print Drivers) functions go in a separate DLL and install with the UI and rendering DLLs during installation time. They are only applicable to kernel-mode drivers, and are now considerd obsolete.

These are the UMPD entry points we know about:

These probably build in a separate directory which should be excluded from the project's top-level DIRS file - we won't need the UMPD components.

There is probably a small component in the UI as well: we've found that the UI's DrvPrinterEvent() has some code in the PRINTER_EVENT_INITIALIZE handler that doing some registry manipulation related to UMPD. Inspect this code and (most likely) remove it.

Note: if the UMPD is actually performing specific functions on the data stream (rather than merely passing the data from the kernel GDI driver back to the spooler), these functions must be merged into the user-mode rendering driver.

Implement <strsafe.h> functions and macros

Printer drivers — whether user mode or kernel — become security nightmares when they have buffer-overflow bugs. At best, it produces system instability when the bugs are tripped inadvertently, and at worst; provide a system compromise at the highest level.

These are to be strenuously avoided.

There is no substitute for careful attention while coding, but buffer overflows are notoriously difficult to get right: this means that one must use all the help one can get to help avoid these.

Some of the most common culprits are the unbounded string operations which do not respect the size of their target buffers:

strcpy(internalBuffer, userString);     // boom

When a user-provided string — a font name, a directory path, a username, etc. — is as expected, it all works well, but when an attacker causes a much longer string to be passed (perhaps via the printing APIs), then the buffer gets overwritten, smashing information past the end of the buffer.

This is where bad things happen, the particular badness depending on lots of factors. But the best case is an application crash, alerting one to the problem.

Microsoft has created a set of libraries which help mitigate many of these problems by providing full buffer-bounding for all the usual string routines: <strsafe.h>:

These largely replace the normal strxxx routines, and the header file actually marks the legacy code as deprecated; this ought to discourage their use.

Converting to the safe-string mechanisms is likely to be tedious, but ought to go a long way to avoiding these buffer overflow issues.

Consider converting to C++

We've long been advocated of using C++ for print-driver development, from back in the NT 3.51 days. User mode printer drivers provide complete support for the usual language features we all know and love, including full exception handling (both SEH and C++ exceptions).

We're not sure how much support for C++ exists for kernel mode drivers, but we can't help but believe that many of these driver authors limited themselves to C only as a defensive measure.

For drivers that require a strict conversion only, with no projected feature development, it may make sense to make as few changes as possible (i.e., leave it in C), but when the product has a future of enhancements ahead of it, a conversion to C++ may enable much more productive development.

However: as much as we favor C++, we nevertheless recommend holding off on the C++ conversion until the existing driver has been largely converted to user mode. We distinguish between mode-change tasks and feature-enhancement tasks, and understand that driver failure can be caused by problems in either.

By doing the bare minimum conversion to user mode first, then working from there. This reduces the number of variables that must be addressed when trying to track down a driver failure.

Test with Print Gremlin

Print Gremlin icon Testing a print driver starts as a manual process just to make sure the overall infrastructure works properly — the DLL loads, the job starts, pages are rendered, debugging is recorded — but large-scale systematic tests get much more tedious.

Print Gremlin is a Microsoft-provided tool in the DDK designed exactly for this purpose: it exercises a printer driver very heavily and helps track down problems. These tests — especially the font tests — are very extensive.

The PGREMLIN.MSI installer is found in the tools\print\x86\ directory of the DDK, and installation requires a reboot due to the many language packs installed. Microsoft's documentation notes that PGREMLIN doesn't clean up after itself, so this suggests using this on a test machine (rather than the developer's box) for fear of messing something up.

PGREMLIN output PGREMLIN is a console-mode application with no user interaction: when launched, it just starts testing. By default it exercises all printers on the box, but can be given the name of one or more particular printers on the command line (perhaps configured via a desktop shortcut).

The Microsoft documentation describes a /SkipComments:"string" option which skips printers with string in the name, but we found that it included those printers, not excluded. This looks like a bug.

On our low-powered test system, the print gremlin produced 32 pages of output (including a cover sheet with summary information discovered about the driver). It took Print Gremlin itself about two minuts to run, and our HP4200 PostScript printer about five minutes to render all the pages.

It seems prudent to make a control run to a known-good printer to have some valid output to compare with the tested driver.

To thoroughly test a driver, one cannot help but actually print real output and visually inspect it, but exercising the internals of the driver is a useful exercise as well. We've seen PGREMLIN produce messages about SEH (Structured Exception Handling) faults during the tests, though it doesn't give any specifics about what prompted the error (i.e., the particular API call in question).

Pausing spooler jobs When testing the internals, one may be more interested in the error messages (or lack thereof) than the output, so it's a viable strategy to Pause the printer in the Printers & Faxes applet so that the print jobs queue up while the tests are run, sending no actual data to the printer.

The jobs can later be released if needed, or Canceled if not.

We'll note that Print Gremlin has a useful behavior: when it's exercising a particular printer, it does direct printing, which effectively loads the rendering DLL into its own address space. Because the DLL is inside the PGREMLIN.EXE process and not under the spooler, it's able to detect all kinds of faults via the Structured Exception Handling mechanism.

This is a big win for the driver developer: rather than have to debug the spooler service — spoolsv.exe — as a whole, one can launch pgremlin.exe directly to watch it run and see its behavior. This makes it much easier to track down serious issues with the rendering DLL.


Resources

While researching this project, we came across several useful references that we're pointing to here. Some directly bear on the task of converting a printer driver from user to kernel mode, while others provide more ancillary information.

Microsoft is notorious for moving links around on their website, so for most MSDN/Technet links we've shown in a small sidebar the navigational hierarchy used to find that document on their site.

A very special thanks to Dr. Herbert Steiner of HSM Informatik AG for his most extensive assistance on this project.

First published: 2006/07/29 (blogged)