Setting an Abort Procedure

The enhancement that we'll add in the PRINT2 version of our program prevents problems related to disk space. If you try to run PRINT1 when the drive containing the TEMP subdirectory lacks sufficient space to store the full page of graphics output, the NEWFRAME Escape call will return an SP_OUTOFDISK error. This error could result from the presence in the TEMP subdirectory of other temporary print files created by GDI for printing. If the Print Manager were given enough time to send these files to the printer, then the program currently printing could continue. It wouldn't be necessary for Windows to return an SP_OUTOFDISK error to the program. However, the Print Manager cannot transfer these files to the printer because—like all other Windows programs—it isn't receiving messages during the time your program is printing.

This problem is solved with something called an ”abort procedure“. The abort procedure is a small exported function in your program. You give Windows the address of this function using the Escape SETABORTPROC subfunction. If GDI runs out of disk space while creating temporary print files, and if enough space could eventually become available by having the Print Manager send existing print files to the printer, then the GDI module calls the program's abort procedure. The abort procedure then effectively yields control to allow the Print Manager to print.

Let's look first at what's required to add an abort procedure to the printing logic and then examine some of the ramifications. The abort procedure is commonly called AbortProc, and it takes the following form:

BOOL FAR PASCAL AbortProc (HDC hdcPrn, short nCode)

{

[other program lines]

}

The function must be listed in the EXPORTS section of your module definition file. Before printing, you must obtain a pointer to this function from MakeProcInstance:

FARPROC lpfnAbortProc ;

[other program lines]

lpfnAbortProc = MakeProcInstance (AbortProc, hInstance) ;

You then set the abort procedure using the Escape SETABORTPROC subfunction. The lpsDataIn parameter is the pointer returned from MakeProcInstance:

Escape (hdcPrn, SETABORTPROC, 0, (LPSTR) lpfnAbortProc, NULL) ;

You make this call before the STARTDOC Escape call. You don't need to ”unset“ the abort procedure after you finish printing.

While processing the NEWFRAME Escape call (that is, while playing the metafile into the device driver and creating the temporary printer output files), GDI frequently calls the abort procedure. The hdcPrn parameter is the printer device context handle. The nCode parameter is 0 if all is going well or is SP_OUTOFDISK if the GDI module has run out of disk space because of the temporary printer output files.

AbortProc must return TRUE (nonzero) if the print job is to be continued and returns FALSE (0) if the print job is to be aborted. If AbortProc receives an nCode parameter of SP_OUTOFDISK and returns FALSE, then the NEWFRAME Escape call currently in progress returns an SP_APPABORT error code (equal to -2), and the print job is aborted.

The abort procedure can be as simple as this:

BOOL FAR PASCAL AbortProc (HDC hdcPrn, short nCode)

{

MSG msg ;

while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return TRUE ;

}

This function may seem a little peculiar. In fact, it looks suspiciously like a message loop. What's a message loop doing here of all places? Well, it is a message loop. You'll note, however, that this message loop calls PeekMessage rather than GetMessage. I discussed PeekMessage in connection with the RANDRECT program at the end of Chapter 12. You'll recall that PeekMessage returns control to a program with a message from the program's message queue (just like GetMessage) but also returns control if there are no messages waiting in any program's message queue.

The message loop in the AbortProc function repeatedly calls PeekMessage while PeekMessage returns TRUE. This TRUE value means that PeekMessage has retrieved a message that can be sent to one of the program's window procedures using TranslateMessage and DispatchMessage. When there are no more messages in the program's message queue, Windows allows other programs to process messages from their queues. When there are no more messages in any program's message queue, Windows returns control to the program calling PeekMessage (that is, to the AbortProc function). The return value of PeekMessage is then FALSE, so AbortProc returns control to Windows.

The PRINT1 version of our program doesn't yield control during the entire time it is printing. Windows is essentially frozen during that time because no other program can process messages. As you've probably discovered by now, the PrintMyPage function in PRINT1 can take a while. Only when the function is finished can the Print Manager actually start to print. An abort procedure gives the Print Manager—and other programs running under Windows—a chance to run while a program is printing.