
MINIDRIVER SAMPLE

A SHADOW-DC, PM PRINTER DRIVER SKELETON CODE BASE

MONTE COPELAND





=========================================================================
INTRODUCTION

The MiniDriver is C source code for a skeleton, 32-bit, rasterizing, OS/2
Presentation Manager printer device driver. It targets a raster-capable
hardcopy device. This code will run on all 32-bit versions of OS/2 with the
32-bit Graphics Engine (GRE). OS/2 2.0 plus Service Pak was the first
release of 32-bit OS/2 with the 32-bit GRE.

Shadow DC drivers, like this one, have some problems. (See below.) However,
the OS/2 Warp Graphics Engine (GRE version 0x0220) eliminates the need to
use shadow DC. If you can afford to build a driver that pre-requisites
Warp, see MiniDriver2. It is a better printer driver model.

This mini printer driver does not print! It does not contain enough
device-specific code to operate a printer! Instead of printing, it bitblts
the image to the display. A developer should grep the code for
DEVICE_SPECIFIC to find the places that need work.

This MiniDriver does not journal and band. Journaling and banding is a
technique to conserve memory. MiniDriver2 does journal and band using
routines in the new Generic Printer Library.

The MiniDriver uses hard-coded breakpoints; thus, the debug version of the
driver requires the OS/2 debug kernel to be installed. See the section on
debugging support in the MiniDriver for more information.

This readme covers these topics:

        .  memory management
        .  exception management
        .  debugging
        .  shadow DC
        .  color considerations




=========================================================================
MEMORY MANAGEMENT

HEAPS AND THE OS/2 SUBALLOCATION PACKAGE

OS/2 32-bit memory management uses Intel 80x86 paging architecture. All
memory operations are page granular with the page size being 4096 (4K)
bytes.

A call to DosAllocMem() will always round up the requested memory size to
the next higher even multiple of 4K. Since most memory requests are for
memory allocations less than 4K, it is unacceptable to use DosAllocMem()
for lots of small allocations. More memory is wasted than used.

The answer is to use a heap. OS/2 provides a heap management package
within the base operating system. It is called the suballocation package
and consists of several APIs: DosSubSetMem(), DosSubAllocMem(),
DosSubFreeMem(), etc.

Sparse heaps are the best because they impact the system the least. A
sparse heap uses a memory object whose pages are allocated but not
committed until they are really needed. For the call to DosAllocMem(),
leave off the PAG_COMMIT flag. For the call to DosSubSetMem(), use the
DOSSUB_SPARSE_OBJ flag. This combination will ensure the OS/2 heap manager
commits pages as they are needed. Allocated pages that are not committed
have a low impact on the system.



SHARED MEMORY OBJECTS

For shared memory objects, OS/2 reserves a place in every process' virtual
linear address space for memory objects that have the "shared" attribute.
OS/2 will map the MiniDriver's shared memory object into every process
that asks for it. When mapped, OS/2 guarantees that the virtual address of
this memory object is the same across all processes that gain access to
the object. OS/2 ensures that there exists a single set of physical 4K
pages to contain the shared memory object. OS/2 uses per-process page
tables to map this single set of physical pages into each process' virtual
address space.

Thus, data items that are global and unchanging in nature belong in a
shared memory object including one-of-a-kind data items. Examples of data
items that have a place in shared memory are

strings: used for message/dialog boxes; printer escape sequences; strings
that name global semaphores

traits: physical, unchangeable attributes of the supported hardcopy
devices like how many input trays it has, the size and number of hardcopy
forms it supports; the names of the individual devices supported by a
hardcopy driver

handles: to the driver module

addresses: to the shared heap

Carefully identifying and placing global data items into shared memory
ensures efficient use of computer memory, no data redundancy, and
data integrity.

In the MiniDriver there are two shared memory objects. One is set up at
compile time using the IBM C Set/2 data_seg pragma. See the use of this
pragma in DRIVER.C.

                   #pragma data_seg( Global )

This pragma causes the compiler to place subsequent data declarations in
DRIVER.C into the memory object (32-bit segment) named "Global." The
object "Global" has the SHARED attribute according to the SEGMENTS
statement in DRIVER.DEF.

The other shared memory object is a heap, and it is allocated at run time.
It is used as a global, shared heap under the management of the OS/2
suballocation package. A pointer to the base of the shared heap is stored
in the "Global" memory object. See INITTERM.C




MEMORY OBJECTS PRIVATE TO THE PROCESS

The MiniDriver keeps some data items on a per-process basis. Data items
that lend themselves to storage in process private space are

DDCs: device drawing contexts are kept per process as the process creates
DCs using DevOpenDC; storage for DDCs are done at run time as required by
the requesting process

handles: to semaphores or perhaps a per-process heap

When the driver is instantiated for each process, a data structure of type
PROCDATA is created in process private space (PROCDATA is defined in
DRIVER.H and declared in DRIVER.C). Using C Set/2 pragmas, the programmer
directs the compiler to place this structure into process private space.

                    #pragma data_seg( PerProcess )

This ensures that for every process that instantiates the MiniDriver,
there will be a separate copy of PROCDATA items to be kept per process.
(Items kept per process are assigned at run time. See INITTERM.C.)

There can be many private memory objects allocated at run time to hold DDC
(device drawing context) information. These are allocated as a result of
the application program calling DevOpenDC() and the graphics engine calling
the driver in turn. The MiniDriver allocates one private object for every
DDC opened then uses the OS/2 suballocation package to manage the per-DDC
heap. (See structure definitions for physical device block and the DDC in
DRIVER.H.)







=========================================================================
EXCEPTION MANAGEMENT


Per-thread exception management is new in OS/2 2.x. It is available to
32-bit EXEs, DLLs, and presentation drivers. OS/2 designers intend for the
per-thread exception management model to replace the traditional
DosExitList() processing found in 16-bit OS/2 programming.

In the future, OS/2 designers intend for the GRE to unload printer drivers
when not in use. Using per-thread exception management instead of
DosExitList() will make your printer driver unloadable.

For more information, read "32-bit OS/2 Exception Management" in Developer
Connection News Volume 2. From DevCon CD Disk 1 (Volume 3 or later), enter

                          VIEW \DOCS\DEVCON2





=========================================================================
INTEGRATED DEBUGGING STRATEGY

Integrated debugging simply means that debugging support is built into the
driver. The printer driver developer writes debugging code which is
compiled and linked into the debug version of the driver.

At the heart of this strategy is conditional compilation using C macros.
Debugging code is useful during development of a driver, but must be
removed for the final product. The C language provides conditional
compilation through macros and the preprocessor. The throw of a compiler
switch removes the debugging code from the driver when building the final
version of the product.

The code for the MiniDriver builds retail, debug, and IPMD versions. The
debug and IPMD versions contain additional code that participates in the
debugging of the driver. This code vanishes in the retail version. The
debug version of the MiniDriver requires the OS/2 debug kernel.





THE OS/2 DEBUG KERNEL

The OS/2 debug kernel is a specially-built kernel that replaces the retail
kernel (the one normally shipped with OS/2) and adds debugging support
like

        .   breakpoints
        .   debug register breakpoints
        .   memory dumps and queries
        .   hot patching
        .   reverse assemble
        .   symbolics


For more information, read "The OS/2 Debug Kernel" in Developer
Connection News Volume 4. From DevCon CD Disk 1 (Volume 5 or later), enter

                          VIEW \DOCS\DEVCON4





CONDITIONAL COMPILATION

Conditional compiliation lets developers build multiple versions of their
software by including or excluding sections of code bracketed with #if and
#endif pairs. For example

        #ifndef NDEBUG
            if( n == 0 )  printf( "you're gonna divide by zero!" );
        #endif

        avg = sum / n;

At run time, if n is zero, the code tells you what is wrong.

#ifndef NDEBUG is a test to see if symbol NDEBUG is not defined; if not,
then include the bracketed section. This is the style
adopted by ANSI C. See the ASSERT.H that comes with IBM C Set/2.





DBPRINTF MACRO

Of all debugging techniques used in C programming, one of the most common
and useful is printf(). Many C programs have been debugged cleanly using
nothing but printf(). It follows that a printf() feature would be useful
in printer driver development.

The MiniDriver implements a DBPRINTF(()) macro. It is enabled in the debug
build of the driver, and vanishes in the retail build.

The output of the DBPRINTF macro is the debug terminal via PMWIN.DLL and
PMDD.SYS. After formatting the string in the worker function dbprintf(),
notice the call to DebugOutput (see dbprintf() in assert.c). This routine
resides in PMWIN.DLL, and it relies on the ring zero device driver,
PMDD.SYS, to drive the COM port hardware and send the data to the debug
terminal.

You must prepare PMDD.SYS for this task by adding a switch on PMDD.SYS
line in CONFIG.SYS:

                   DEVICE=C:\OS2\PMDD.SYS /C2

where /C2 targets the output at COM2, /C1 sends output to COM1, etc.
Notice that the function DebugOutput has been imported with an ordinal in
the MiniDriver DEF file.

printf() is a C function that has a variable number of arguments. This
presents a small problem for a C macro implementation of DBPRINTF(()). C
macros accept a fixed number of parameters. (This is definitely true for
IBM C Set/2.) In order to submit a variable number of parameters to the
DBPRINTF(()) macros, bracket the parameter list in an extra set of
parenthesis. A typical DBPRINTF(()) macro invocation will look like this:

         DBPRINTF(( "Rectangle count = %d\n",  lCount ));

The debug definition of the DBPRINTF(()) macro is

         #define DBPRINTF(str) dbprintf str







THE assert() MACRO

The verb assert means to state or express positively. Years ago, Kernighan
and Ritchie turned this verb in to a C language debugging macro. Assume
this prototype for assert():

                  void assert( int fTest );

Then you might assert that you successfully requested a semaphore with

        rc = DosRequestMutexSem( pddc->hsem, SEM_INDEFINITE_WAIT );
        assert( rc == 0 );

The MiniDriver has its own version of ASSERT.H to use instead of the one
that came with the C compiler. It differs in a few ways. It does not write
to stderr nor does it abort the program. When an assertion fails, the
MiniDriver writes the asserted expression, the filename, and the line
number to the debug terminal using the DBPRINTF(()) macro. It beeps to let
you know something unexpected happened. Then it executes a hard-coded
interrupt 3 -- the Intel breakpoint instruction -- which is handled by the
OS/2 debug kernel. The interrupt 3 stops OS/2 execution on the test
machine and dumps the registers and the current instruction on the debug
terminal. If you decide the failed assertion is not fatal, just tell the
debugger to GO with the G command.




IPMD

The IBM C Set/2 compiler includes a PM debugger IPMD.EXE. To build a
version of the MiniDriver for IPMD, enter

                NMAKE IPMD

IPMD will not allow a DLL-load occurence breakpoint until you modify the
LIBPATH statement in CONFIG.SYS to include the subdirectory where your
printer driver is installed.









=========================================================================
THE SHADOW DC


Since OS/2 1.1, printer driver developers realized they could "steal"
functions from the system display driver and save writing some code. They
would create a memory DC in the display driver, select a bitmap into it,
then relay their drawing orders to the display driver. The display driver
would render the graphics into the bitmap on behalf of the printer driver.
When asked to print a page, the printer driver would get the bitmap bits,
put printer escape codes around them, then send them to the printer.

In essence, the memory DC in the display driver would "shadow" the DC of
the printer.

Such 16-bit printer drivers are called "brute force" because they use
DosLoadModule() to load DISPLAY.DLL then pretend to be the graphics engine
as they make calls into the display driver.

The MiniDriver shows a 32-bit technique for using shadow DCs. During DC
enabling, the printer driver calls GreOpenDC to create an OD_MEMORY DC in
the display. This will be the shadow DC. This DC comes from the
currently-installed display driver, not the 16-bit DISPLAY.DLL.

Having created the shadow DC, the MiniDriver creates a bitmap and selects
the bitmap into it. When the application draws on the printer DC, the
MiniDriver relays the drawing orders to the shadow DC to do the work.



RELAYING DRAWING ORDERS


To relay drawing orders to the shadow DC, the MiniDriver uses either a Gre
or InnerGre call. (Technically, Gre and InnerGre calls are not function
calls, but macros. See PMDDIM.H from the Toolkit for the Gre macros and
INNERGRE.H from the MiniDriver source for the InnerGre macros.)

In the general case, the graphics engine calls the MiniDriver with the
printer DC handle and other parameters. The MiniDriver substitutes its DC
handle with the shadow DC handle then calls the corresponding Gre/InnerGre
function.

Gre calls expect world coordinates and logical identifiers (LCIDs) much
like the GPI interfaces do at the application level. However, there are
times when a 32-bit driver must relay operations to the shadow DC at the
device level. In these cases, a driver must call the InnerGre entries.

The Inner GRE entries differ from Gre entries in a few ways.


. Inner GRE activities are never journaled.

. Command flags (part of the last parameter to the Gre calls) are not
reloaded from the graphics engine's DC. The graphics engine will use COM_*
flag as supplied by Inner GRE caller.

. HDC validation is not performed at the Inner GRE level. The graphics
engine assumes that the HDC supplied on the call is valid.

The MiniDriver is meant to serve as an example of when to use Gre vs.
InnerGre for a given mandatory function.



SHADOW DC OUTPUT


The MiniDriver does not generate printer-specific output, but it does send
output to the display. See the file NEWFRAME.C. There is some debugging
code which bitblts the shadow DC bits onto a PS for the HWND_DESKTOP.

The first bitblt is a one-to-one bitblt. It is very fast, but you only see
the lower left corner of the entire bitmap.

The second bitblt shrinks the entire page to fit onto the display. This
bitblt is a stretch blt, so it does take some time.

After each blt, the MiniDriver hits a hard-coded breakpoint which causes a
system stop. Enter G for go at the debug terminal to resume execution.



PROBLEMS WITH THE SHADOW DC


There are problems with the shadow DC approach to 32-bit printer driver
development.

Generally, printed output will not be consistent from display to display.
Because the printer DC is so dependent on the currently-installed display
driver, you will probably get different printed output on a VGA than an
XGA system.

Area fills performed by the shadow DC appear to be too fine-grained when
printed on laser printers. This is because the area-fill patterns in the
display driver are meant for display devices with a resolution of 60-80
pels per inch. Today's laser printers have resolutions of 300 and 600 pels
per inch, and display fill patterns appear smaller on hard copy than on
the screen.

As a printing thread enters the display driver, the display driver probably
requests a global semaphore. If so, other threads, including the shell and
applications, will slow down because they are in contention with the
printing thread for display driver resources.




ALTERNATIVES TO THE SHADOW DC

The Graphics Engine in OS/2 Warp has a full suite of rasterization
routines. See the MiniDriver2 sample code which exploits this new
technology.

For more information, read "New and Improved, The GRE22 Printer Device
Driver Model" in Developer Connection News Volume 5. From DevCon CD Disk 1
(Volume 6 or later), enter

                          VIEW \DOCS\DEVCON5


In the Device Driver Kit, there is code for the XGA display driver and the
32-bit 42XX printer driver. These drivers have a suite of rasterization
routines which, if integrated, would eliminate dependencies on the display
driver and the shadow DC.





=========================================================================
COLOR CONSIDERATIONS


The MiniDriver, as supplied, creates a monochrome bitmap which it selects
into the shadow DC. The size and kind of bitmap to create is a
device-specific decision. If the printer is color capable, then one would
probably create a color bitmap at DC enable time. See ENABLE.C.

In general, display drivers do a consistent job of keeping one-bit-per-pel
monochrome bitmaps. However, display driver handling of color bitmaps
tends to be less consistent.

Display drivers, when asked to handle bitmaps, provide a uniform interface
to the printer driver for creating, setting, and getting bitmap bits. But
display drivers store color bitmaps in an internal representation
optimized for the display hardware. After all, they are display drivers.

Here are example scenarios to consider when creating color bitmaps in the
shadow DC.

1) If you create a 16-color bitmap (four bits per pel) in a display driver
shadow DC for a display capable of 24-bit-per-pel color, there is a good
chance that the display driver will store the four-bit-per-pel bitmap
internally with 24 bits in order to be compatible with its hardware. For
performance reasons, it is faster to bitblt from a memory DC to the screen
if the source (memory) DC bitmap is of the same ilk as the target.

This means the display driver will use more memory to hold the bitmap than
you might expect. Consider a color printer, capable of 16 colors (four
bits per pel), loaded with Letter-sized paper, with a printable area 8 x
10.5 inches, and resolution of 300 dots per inch. It would take
approximately 4MB of main computer memory to hold the entire bitmap for
the page.

If the printer driver creates a 4-bit-per-pel bitmap, but the display
driver stores the bitmap internally with 24 bits per pel, then the memory
required to hold the bitmap jumps to nearly 23MB. The swapper file would
grow considerably. The printer driver would have to implement banding to
reduce the size of the swapper file.

2) Conversely, it is possible to create a 24-bit-per-pel bitmap in a VGA
display driver DC. The display driver could well store the bitmap
internally in four bits per pel. One might expect 16 million colors and
only get 16.

Generally, display drivers are capable of bitmap formats with more colors,
but line drawing, pattern filling, and other drawing support needed for
shadow DC operation is available on one bitmap format, the one best suited
for the display hardware.





=========================================================================
RELATED READING

COPELAND, MONTE (1993) "32-bit OS/2 Exception Management" Developer
Connection Volume 2.

COOPER/COPELAND (1994) "New and Improved: the GRE22 Graphics Engine"
Developer Connection Volume 5.

COPELAND/SCHMITT (1994) "The OS/2 Debug Kernel" Developer Connection News
Volume 4.

KOGAN, MICHAEL (1992a), "OS/2 2.x Memory Management," The Design of OS/2,
Addison-Wesley, p. 163.

KOGAN, MICHAEL (1992b), "OS/2 2.x Exception Architecture," The Design of
OS/2, Addison-Wesley, p. 224.

IBM et al. (First edition, April 1992), "C Set/2 #pragma Extensions," C
Set/2 User's Guide Version 1.00," IBM Programming Family, p. 112.

Mastriani, Steve (1992) "Debugging OS/2 2.0 Device Drivers," Writing OS/2
2.0 Device Drivers in C, Van Nostrand Reinhold, p. 191.

