Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Printing From Windows 3


MAR92: PRINTING FROM WINDOWS 3

PRINTING FROM WINDOWS 3

Writing an abort procedure

Michael J. Young

Michael is an author of computer programming books and a Microsoft Windows developer. He can be reached at 20 Sunnyside Avenue, Suite A, Mill Valley, CA 94941, or through CompuServe, ID 75156.2572.


Although the final results can be gratifying, writing code to print under Microsoft Windows is not a simple task. One of the most confusing elements, and yet one of the most important, is the "abort procedure." This article explains why a Windows print routine needs to have an abort procedure, and describes exactly what goes on during a print job when an abort procedure has been installed. It also provides the code for a sample abort procedure.

Windows Printing

Under Windows, printing is similar to sending output to a window. You first obtain a handle to a device context for the printer; you can then display text or graphics on the printer using the standard Windows GDI (Graphics Device Interface) functions, such as TextOut or Ellipse. When printing, however, you also need to manage the print job by making calls to the Windows Escape function, which provides an entire family of printer services; you specify the particular service you want by passing a code as the second parameter.

The basic steps for printing under Windows are as follows:

  1. Call CreateDC to obtain a handle to a device context for the printer.
  2. Initiate the print job by calling Escape, passing the STARTDOC function code.
  3. Display the desired text or graphics on the page by calling standard Windows GDI functions, passing the handle obtained in step 1.
  4. When the page is complete, call the NEWFRAME Escape function to signal Windows to begin printing the page.
  5. Repeat steps 3 and 4 for any additional pages in the document.
  6. Terminate the print job by calling the ENDDOC Escape function.
  7. Call DeleteDC to release the printer device context.
When you make the NEWFRAME Escape call, the Windows GDI creates one or more temporary print files on disk, based upon the output commands you have issued, and activates the Windows Print Manager. The Print Manager copies each of the print files to the printer, and deletes each file after it has been copied.

There is a serious problem, however, with the printing procedure just outlined. A typical Windows print job can be lengthy, during which time, Windows message processing is blocked. The ramifications of this are that: 1. The program performing the printing cannot process mouse or keyboard input messages, so there is no way for the user to signal the program to stop the print job once it has begun; and 2. other Windows programs are prevented from running. The user, therefore, cannot accomplish useful work in other applications while printing is taking place. Furthermore, the Print Manager itself is unable to run and cannot begin copying print files to the printer until the program completes the entire printing routine and returns to retrieve another message. As a result, the generation of the printed copy is delayed. Also, the GDI may run out of disk space for storing print files (which can be quite large). Because the Print Manager is not allowed to run, it cannot free disk space by deleting the print files that it has already copied to the printer.

Figure 1 illustrates the basic flow of program control during printing. As you can see in this figure, messages cannot be dispatched and other programs cannot run until the entire print routine is finished.

The ideal solution to these problems would be to have the Windows GDI periodically call the program during processing of the NEWFRAME Escape, so that the program could process messages and yield control to other applications on a regular basis. Fortunately, there is a way to do exactly that: Install an abort procedure.

The Abort Procedure

An abort procedure is a function you define within your program. If you install an abort procedure before initiating the print job, the GDI will call it periodically while processing any subsequent NEWFRAME Escape calls.

The abort procedure given here has the form: BOOL FAR PASCAL AbortFunc (HDC HPrnDC, short Code);. The function AbortFunc represents the function name; you can choose any name you want. The HPrnDC parameter is the handle to the printer device context. The Code parameter indicates whether or not the GDI has run out of disk space for creating temporary print files. It will have one of two values: 0 if there is sufficient disk space, or SP_OUTOFDISK if the GDI has run out of disk space but more disk space could be made available by allowing the Print Manager to run. If the abort procedure returns a nonzero value, the print job will continue. If it returns 0, the print job will be cancelled.

A Sample Abort Procedure

The abort procedure given here is designed to allow other programs to run during the print job, and also to permit the user to stop the print job at any time by clicking a button displayed in the program window, or by pressing the Esc key. The definition for the abort procedure, AbortProc, is contained in Example 1.

Example 1: The definition for the abort procedure

  BOOL FAR PASCAL AbortProc (HDC HPrnDC,short Code)
       {
       MSG Msg;

       while (!UserAbort && PeekMessage (&Msg,NULL,O,O,PM_REMOVE))
            {
            TranslateMessage (&Msg);
            DispatchMessage (&Msg);
            }
       return (!UserAbort);

       } // end AbortProc

The heart of the abort procedure example is a message loop, which extracts and dispatches messages until the program's message queue is empty. To avoid blocking the print job, the procedure calls PeekMessage--which returns immediately even if no message is available--rather than GetMessage. When there are no more program messages, PeekMessage returns FALSE, which causes the abort procedure to return control to the GDI so that printing can continue.

PeekMessage is one of the Windows functions that yields control to other programs in the system. Therefore, each time the GDI calls the abort procedure during the print job, other applications, including the Print Manager, are allowed to run. The user can therefore work within other programs while printing continues in the background. Also, the Print Manager can start copying print files to the printer--and then deleting these files--while the GDI is still processing print commands, accelerating the generation of printed output and alleviating any lack of disk space. (Consequently, the abort procedure need not perform any special action if the Code parameter indicates an out-of-disk-space condition.)

Also, because the abort procedure dispatches all program messages (by calling DispatchMessage), the program can continue to receive input, allowing the user to terminate the print job by clicking the "Cancel Printing" button, or by pressing Esc.

Figure 2 illustrates the main flow of program control during printing with the abort procedure installed. Compare this illustration with Figure 1. Notice that the abort procedure returns a value based upon the current value of UserAbort, which is a global program variable. It is set to FALSE immediately before the print job starts; if the user clicks the "Cancel Printing" button or presses Esc, the appropriate message-handling routine within the program's window procedure must set it to TRUE. The abort procedure returns TRUE if UserAbort is FALSE, so that the print job keeps running; it returns FALSE if UserAbort is TRUE, causing the GDI to terminate printing of the current page and to return immediately from the NEWFRAME Escape call. Also, if UserAbort is TRUE, the abort procedure immediately terminates the message loop to provide a speedy response to the user's input.

The abort procedure is called from within the Windows environment, so you must export it by including its name in the list of functions following the EXPORTS statement in the linker definition file.

Supporting Code for the Abort Procedure

Example 2 contains the general code for installing the sample abort procedure and for managing the print job. This code should be contained within the branch of the main window procedure that processes the program's print command.

Example 2: The code for installing the sample abort procedure and for managing the print job

  HWND HAbortButton;
  HANDLE HInst;
  HDC HPrnDC;
  BOOL InPrintJob = FALSE;
  FARPROC PtrAbortProc;
  BOOL UserAbort;
    .     .     .
  if (InPrintJob)
       return (NULL);
  HPrnDC = GetPrnDC ();
  ShowWindow (HAbortButton,SW_SHOW);
  UserAbort = FALSE;
  InPrintJob = TRUE;
  PtrAbortProc = MakeProcInstance (AbortProc,HInst);
  Escape (HPrnDC, SETABORTPROC, 0, (LPSTR)PtrAbortProc,NULL);
  Escape (HPrnDC, STARTDOC, 10, (LPSTR) "Print Demo", 0L);

  // Calls to GDI output functions, such as TextOut and Ellipse go here.
  Escape (HPrnDC, NEWFRAME,0,0L,0L);
  Escape (HPrnDC, ENDDOC,0,0L,0L);
  FreeProcInstance (PtrAbortProc);
  InPrintJob = FALSE;
  ShowWindow (HAbortButton,SW_HIDE);
  DeleteDC (HPrnDC);

Before starting the print job, the global flag InPrintJob is set to TRUE, and after the print job it is set back to FALSE. (It is initialized to TRUE.) This flag can be used throughout the program to determine whether a print job is in progress. Remember that the abort procedure dispatches program messages, and therefore the window procedure may be invoked during printing. Whether or not a print job is in progress affects the way certain messages must be processed. For example, the print routine in Example 2 returns immediately if InPrintJob is TRUE to prevent starting another print job while one is already in progress. As another example, the window procedure should process the WM_CLOSE message, destroying the window only if InPrintJob is FALSE.

Before starting the print job, the routine calls ShowWindow to display the "Cancel Printing" push-button control that allows the user to abort the print job. This button should have been created--without making it visible--by calling CreateWindow within the program instance initialization routine. The routine calls ShowWindow again after printing to hide the button, as it is no longer needed.

Before starting the print job, the routine sets UserAbort to FALSE. This flag should be set to TRUE by the window procedure if the user clicks the "Cancel Printing" button or presses Esc during printing.

The Windows MakeProcInstance function is called to obtain the procedure instance address of the abort procedure, AbortProc. After the print job, this address (stored in the pointer PtrAbortProc) is released by calling FreeProcInstance. Note that the call to FreeProcInstance must come after the ENDDOC Escape call.

The SETABORTPROC Escape function is called to install the abort procedure. Note that this function must be passed the procedure instance address of the abort procedure obtained in the previous step. The SETABORTPROC Escape call must be made before initiating the print job with the STARTDOC Escape.

For simplicity, the example listing does not check error return codes. In general, if the Escape function fails, it returns a negative error code, and the print routine should exit immediately, without calling the ENDDOC Escape. (The GDI automatically aborts the print job.) Note that--contrary to the SDK documentation--if the abort procedure returns FALSE to abort the print routine, the NEWFRAME Escape function does not return an error code (unless an actual error occurred or the Print Manager is not installed). If the print routine processes more than one page, it should therefore perform the following actions immediately after each call to the NEWFRAME Escape. If the Escape function returns an error code, terminate immediately without calling the ENDDOC Escape, otherwise, check the UserAbort flag. If it is TRUE, call the ENDDOC Escape and terminate immediately, without attempting to print additional pages.

Conclusion

The examples in this article assumed that printing takes place directly from the program window; you could use a similar approach to print from a modal dialog box, displaying the "Cancel Printing" button within this box and processing input messages received during printing within the dialog procedure.

To enhance the routines presented here, you could display an hourglass cursor while the mouse pointer is within the program window. To do this, simply process the WM_MOUSEMOVE message, setting the cursor to an hourglass (by calling SetCursor) only if InPrintJob is TRUE.


_PRINTING FROM WINDOWS 3_
by Michael J. Young

[Example 1]

BOOL FAR PASCAL AbortProc (HDC HPrnDC,short Code)
     {
     MSG Msg;

     while (!UserAbort && PeekMessage (&Msg,NULL,0,0,PM_REMOVE))
          {
          TranslateMessage (&Msg);
          DispatchMessage (&Msg);
          }
     return (!UserAbort);

     } // end AbortProc


[Example 2]

HWND HAbortButton;
HANDLE HInst;
HDC HPrnDC;
BOOL InPrintJob = FALSE;
FARPROC PtrAbortProc;
BOOL UserAbort;
  .
  .
  .
if (InPrintJob)
     return (NULL);
HPrnDC = GetPrnDC ();
ShowWindow (HAbortButton,SW_SHOW);
UserAbort = FALSE;
InPrintJob = TRUE;
PtrAbortProc = MakeProcInstance (AbortProc,HInst);
Escape (HPrnDC,SETABORTPROC,0,(LPSTR)PtrAbortProc,NULL);
Escape (HPrnDC,STARTDOC,10,(LPSTR)"Print Demo",0L);

// Calls to GDI output functions, such as TextOut and Ellipse go here.
Escape (HPrnDC,NEWFRAME,0,0L,0L);
Escape (HPrnDC,ENDDOC,0,0L,0L);
FreeProcInstance (PtrAbortProc);
InPrintJob = FALSE;
ShowWindow (HAbortButton,SW_HIDE);
DeleteDC (HPrnDC);

Copyright © 1992, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.