OS/2 Presentation Manager Drivers
Written by Dave Raymer
Historically, the general population has held the belief that computers are inherently difficult to use. One of the outgrowths of this popularly held belief has been the Graphical User Interface, or more commonly the GUI. The designers of OS/2, both on the Microsoft, and IBM side, strove to provide not only an easy to use interface, but an easy to program interface. This ease is measured by the robustness of the application programming interfaces (API's) that OS/2 supports, as well as the high degree of device independence that is provided by the display and printer subsystems.
The OS/2 display and printer subsystems are built around the OS/2 Graphics Engine, and a set of executable programs collectively termed "Presentation Drivers." Presentation Drivers (PD's) are a special form of dynamic link library (DLL) that provide a predefined interface for the OS/2 Graphics Engine (PMGRE) to use in rendering. The display driver is one form of a presentation driver, and the printer/plotter driver is another. The primary difference between the two is that the display driver must provide support for a pointing device, but on the whole, the functionality of the two is essentially the same.
In general, Joe and Suzi User interact with OS/2 through the WorkPlace Shell, which is an application that makes extensive use of two DLLs, PMWIN.DLL and PMGPI.DLL. These two DLLs are responsible for providing the API interface that application programmers use. In turn, PMWIN and PMGPI, use the PMGRE in a manner very similar to the shell's use of PMWIN and PMGPI. The PMGRE in turn uses the PD to produce the physical output that the user sees and interacts with, whether this output is on the video display, or a piece of paper. Figure 1 provides an annotated stack model of this simplified view of the OS/2 user interface.
|WorkPlace Shell||← User interacts with|
|PMWIN.DLL PMGPI.DLL||← WorkPlace interacts with|
|PMGRE.DLL||← PMWIN/PMGPI interact with|
|<presentation driver>||← produces visual output|
|<physical device>||← displays visual output|
A Brief Overview Of The Printer Subsystem
The OS/2 Print Subsystem consists of three primary components, the print manager, the print spooler, and the presentation driver. Under OS/2 version 2.x the print manager is implemented through WorkPlace Shell printer objects. The WPS printer object provides the user the ability to manage print jobs on a per printer basis, but is not the focus of this article, so little more will be said in regards to it.
OS/2 provides two print spoolers, PMPRINT and PMPLOT, which differ in the fact that PMPLOT supplies a reverse vector clipping engine developed by Steve Matthews for Micrografx, Incorporated, and reimplemented by Wes Bell, also of Micrografx, for IBM, enabling HPGL plotters to produce correct output in what is primarily a raster oriented environment. The purpose of the print spooler is to build a meta-data file of an applications request for hardcopy output, allowing the application to continue processing without without the need to wait for hardcopy output rendering to complete.
The OS/2 print spoolers provide two types of meta-data files, the first is a result of opening the printer OD_QUEUED with the PM_Q_STD standard option. In this mode, the spooler will produce an intermediate data file that contains boundary information as well as the application supplied arguments to the rendering functions. The second format occurs when the printer is opened OD_QUEUED with the PM_Q_RAW option. This combination will produce an intermediate data file that is the raw printer data stream. However, the overall data path through the printer will remain the same.
When used in conjunction with the spooler, the data is passed through the presentation driver twice. In the PM_Q_STD mode, the first pass is used to accumulate bounds and do basic error checking on the application supplied arguments, while the second pass, initiated by the spooler, will generate the hardcopy output. In the PM_Q_RAW format, the meta-data file created contains raw printer data stream, and will pass through the presentation driver unmodified on the second pass.
The Presentation Driver
The OS/2 presentation driver is built around the concept of the dispatch table. The dispatch table belongs to the OS/2 graphics engine, and is passed to the driver at a specified point during the enable processing, so that the presentaion driver can override the default processing of certain key rendering routines. The enable processing for a presentation driver is handled through a procedure that is aliased to or called OS2_PM_DRV_ENABLE. The following snipet from a presentation driver module definition file's export section aliases a procedure "Enable" to OS2_PM_DRV_ENABLE, and places it at ordinal 200 in the driver. Placing the OS2_PM_DRV_ENABLE procedure at ordinal 200 is required.
OS2_PM_DRV_ENABLE = Enable @200
The prototype for the Enable procedure, for the CSet/2(tm) compiler package, is as follows.
ULONG _System Enable(ULONG SubFunc,ULONG Param1,ULONG Param2);
The returned value from the enable procedure varies from subfunction to subfunction.
The following table lists the subfunctions.
The handling of each of these subfunctions is covered in some detail in the OS/2 Toolkit white books (specifically the I/O Subsystem Presentation Driver reference), available from IBM, therefore only a brief summary of each will be provided here.
- This ENABLE subfunction is called when the driver is loaded. Its purpose is to initialize the logical device block, which includes setting some bits telling the engine whether the PD needs a physical device block (PDEV) per DC and to hook the PMGRE entry points that the PD wishes/needs to support.
- This ENABLE subfunction is called each time a DevOpenDC is requested by the application. The purpose of this function is to create and allocate a physical device block, which should contain any information needed by the PD to perform I/O to the specified device. Typically these are things such as a Presentation Manager Anchor Block (HAB), a "private" memory heap, and a copy of the DEVOPENDATA, passed in to Enable() for this subfunction via Param1.
- This ENABLE subfunction is called when a physical device block is to be deleted. The major purpose of this function is to release any memory allocated in Fill_Physical_Device, and to close the pathway to the device.
- This ENABLE subfunction is called each time a DC is created. It is also the first time the PD is presented the DC handle and is the time to allocate the internal DeviceContext. We are also presented with the corresponding physical device pointer returned from the Fill_Physical_Device subfunction.
- This ENABLE subfunction is called when a DC has completed the disable process. The purpose of this function is to release any memory allocated for the PD's internal device context, often referred to as the "cookie."
- This Enable subfunction is called when the engine wishes a device driver to save all DC information. This may be called multiple times and the driver should push the DDC in LIFO order.
- This ENABLE subfunction is called when the engine wishes the PD to POP a saved DC, or POP to a specified DC. The following table shows the action to be taken based on the contents of Param2.
|= 0||then error.|
|> 0||it specifies which state to restore, and the others are lost, (if Number is 2, then the second DC is restored, the first DC is saved, and all others are lost.|
|< 0||it specifies how many states to pop back. If the value is -1, then pop back to the first state|
- This Enable subfunction is called to reset a the current DC, returning it to is initialization state.
- This call informs the PD that the DC has been created by the Engine. Also it is the first time the PD receives the magic cookie returned from the Enable_DC_Begin subfunction. At this time, the PD should open the spooler if the current DC was openned OD_QUEUED. Also any metafiles or journaling is started here.
Journaling is used primarily by raster devices that produce output through "banding." Even a low resolution printer, with 80 by 120 dots per inch would require a substantial amount of memory to represent an entire page of output in memory. By journaling, or recording the calls into the driver, the driver can break the physical page into smaller sections, replaying the journal file for each section. Journaling was very useful in the 1.X releases of OS/2 to handle segmentation induced limitations. However, in Version 2.0+, it can be used to prevent a presentation driver from consuming a large amount of physical memory, which may lead to thrashing.
- This ENABLE subfunction is called before a the graphics engine begins disable processing. This allows the PD to do any final clean up of resources prior to the full disable. This is the point at which the PD ceases journaling, closes the spooler, etc.
Producing Hardcopy Output
Once all ENABLE processing has been completed, the presentation driver is ready to begin producing output. In the handling of the Fill_Logical_Device Enable() subfunction, the PD hooks out a copy of the the PMGRE dispatch table. The heart of the OS/2 graphics engine is a dispatch routine that is used to access presentation drivers and internal PMGRE entry points via vectors. The dispatch table is an array of 32bit entries, each representing the addresses of entry points into the graphics engine. Logically, the presentation driver perform one of four actions on a given entry in the dispatch table.
- Ignore it.
- Copy it.
- Hook it (replace it.)
- Swap it (save and replace it.)
The following macros are taken from my presentation driver template source code, and provide implementations of options two through four.
#define COPY(Fun) \ da##Fun = (PFNL)*(pDispatchTable + \ (NGre##Fun & 0xffL)); #define HOOK(Fun) \ *(pDispatchTable + \ (NGre##Fun & 0xffL)) = (ULONG)Fun; #define SWAP(Fun) \ da##Fun = (PFNL)*(pDispatchTable + \ (NGre##Fun & 0xffL)); \ *(pDispatchTable + (NGre##Fun & 0xffL)) \ = (ULONG)Fun;
Note that the above macros assume that the presentation driver source code provides local storage for SWAP'd and COPY'd vectors in local address space. In my presentation driver template source code, this storage is provided by variables with the prefix "da".
The following are functions which a driver does NOT need to hook, but should save the PMGRE vector. By calling the vector directly, the overhead of the PMGRE dispatch mechanism is avoided. These entry points are COPY'd.
COPY( Convert ); COPY( SelectClipPath ); COPY( ConvertWithMatrix);
The following entry points should be HOOK'd by the PD and processed completely. None of these involve PMGRE call-back, ie, your presentation driver must do all the work.
HOOK( AccumulateBounds ); HOOK( ImageData ); HOOK( Bitblt ); HOOK( CreateLogColorTable ); HOOK( DeviceCreateBitmap ); HOOK( DeviceDeleteBitmap ); HOOK( DeviceGetAttributes ); HOOK( DeviceQueryFonts ); HOOK( DeviceSelectBitmap ); HOOK( DeviceSetAttributes ); HOOK( DeviceSetDCOrigin ); HOOK( DeviceSetGlobalAttribute ); HOOK( DrawLinesInPath ); HOOK( ErasePS ); HOOK( Escape ); HOOK( GetBitmapBits ); HOOK( GetBoundsData ); HOOK( GetCodePage ); HOOK( GetCurrentPosition ); HOOK( GetDCOrigin ); HOOK( GetLineOrigin ); HOOK( GetPairKerningTable ); HOOK( GetPel ); HOOK( GetStyleRatio ); HOOK( LockDevice ); HOOK( PolyScanline ); HOOK( PolyShortLine ); HOOK( QueryColorData ); HOOK( QueryColorIndex ); HOOK( QueryDeviceBitmaps ); HOOK( QueryDeviceCaps ); HOOK( QueryHardcopyCaps ); HOOK( QueryLogColorTable ); HOOK( QueryNearestColor ); HOOK( QueryRealColors ); HOOK( QueryRGBColor ); HOOK( RealizeColorTable ); HOOK( RealizeFont ); HOOK( ResetBounds ); HOOK( SetBitmapBits ); HOOK( SetCodePage ); HOOK( SetLineOrigin ); HOOK( SetPel ); HOOK( SetStyleRatio ); HOOK( UnlockDevice ); HOOK( UnrealizeColorTable );
The following entry PMGRE entry points should also be supported by the presentation driver, but may also be safely passed back to the PMGRE supplied entry points if the processing involves complex clipping, non-device fonts, etc. These entry points may be safely SWAP'd by a printer presentation driver.
SWAP( Arc ); SWAP( BeginArea ); SWAP( BoxBoth ); SWAP( BoxBoundary ); SWAP( BoxInterior ); SWAP( CharString ); SWAP( CharStringPos ); SWAP( DeviceQueryFontAttributes ); SWAP( EndArea ); SWAP( FillPath ); SWAP( FullArcBoth ); SWAP( SetCurrentPosition ); SWAP( FullArcBoundary ); SWAP( FullArcInterior ); SWAP( NotifyClipChange ); SWAP( NotifyTransformChange ); SWAP( PartialArc ); SWAP( PolyLine ); SWAP( PolyMarker ); SWAP( QueryCharPositions ); SWAP( QueryTextBox ); SWAP( QueryWidthTable ); SWAP( SetArcParameters );
The internals of each of the PD supplied entry points is dependent on the physical device type (printer or display, raster or vector), the algorithms and data structures chosen, and of course the developer. It is therefore not within the scope of this article to discuss them further. However, each of the external entry points to the presentation driver, and for that matter, the PMGRE have a common parameter, that is worth discussing. The last parameter to all entry points is a 32 bit unsigned value which contains the entry point id in the low 16 bits and a set of flags in the high order 16 bits. These bits should be checked at the beginning of each entry point procedure, as they contain additional control information governing exactly what the PD should do. For example, on the first pass through the driver, the COM_DRAW bit will be clear, while the COM_BOUND bit will be set. In this case, the PD need only accumulate bounds for the operation, and need not produce any physical output. Other bits of interest are used to indicate whether or not the driver is currently within a path or area bracket.
A Few Final Notes
For a display PD, there are several additional entry points, related to the support of mouse and text cursors. Under V2.0+ of OS/2, the display driver also has the additional overhead of the Virtual Device Driver necessary to support the multiple virtual DOS machines (MVDMs) and WINOS2. Overall, display drivers are more difficult to write than printer drivers due to performance considerations. If you are interested in creating a display Presentation Driver, I would highly recommend contacting IBM through the Developers Assistance Program (the local IBM branch office should be able to help.)
OS/2 Presentation Drivers are not overly complex, neither are they simple. To successfully create a PD requires careful thought and design as well as a strong knowledge of computer graphics in general. The use of modular and structured programming techniques, along with object oriented concepts (one need not use an object language to write object oriented code) will make the development cycle far less frustating, and much more rewarding. Remember that a little extra effort in the design phase can save a great deal of recoding in the testing phase.