August 92 - LIVING IN AN EXCEPTIONAL WORLD
LIVING IN AN EXCEPTIONAL WORLD
SEAN PARENT
Handling exceptions is a difficult but important part of developing Macintosh
applications. This article provides a methodology as well as a set of C tools for handling
exceptions and writing robust code. Techniques and examples are provided for dealing
with some of the Toolbox idiosyncrasies, and some interesting features of the C
preprocessor, MacsBug, and MPW are explored.
Writing software on the Macintosh can be difficult. Writing robust software on the
Macintosh is even more difficult. Every call to the Toolbox is a potential source of a
bug and there are too many cases to handle--what if there isn't enough memory, or the
disk containing the code has been ejected, or there isn't enough stack space, or the
printer is unplugged, or . . . The list goes on, and a well-written application is
expected to handle every case--always recovering without loss of information. By
looking at how software is developed, this article introduces a methodology and tools
for handling the exceptional cases with minimal impact on the code that handles the
task at hand.
VERSION 1: NORMAL FLOW OF CONTROL
When writing code, programmers usually begin by writing the normal flow of
control--no error handling. The code shown below is a reconstruction of the first
version of a printing loop routine that eventually went out as a Macintosh Technical
Note, "A Printing Loop That Cares . . ." (#161). Note that comments were removed to
make the structure more apparent.
void PrintStuff(void)
GrafPtr oldPort;
short copies, firstPage, lastPage, numCopies,
printmgrsResFile, realNumberOfPagesInDoc,
pageNumber;
DialogPtr printingStatusDialog;
THPrint thePrRecHdl;
TPPrPort thePrPort;
TPrStatus theStatus;
GetPort(&oldPort);
UnLoadTheWorld();
thePrRecHdl = (THPrint)NewHandle(sizeof(TPrint));
PrOpen();
printmgrsResFile = CurResFile();
PrintDefault(thePrRecHdl);
if (PrStlDialog(thePrRecHdl)) {
realNumberOfPagesInDoc = DetermineNumberOfPagesInDoc(
(**thePrRecHdl).prInfo.rPage);
if (PrJobDialog(thePrRecHdl)) {
numCopies = (**thePrRecHdl).prJob.iCopies;
firstPage = (**thePrRecHdl).prJob.iFstPage;
lastPage = (**thePrRecHdl).prJob.iLstPage;
(**thePrRecHdl).prJob.iFstPage = 1;
(**thePrRecHdl).prJob.iLstPage = 9999;
if (realNumberOfPagesInDoc < lastPage) {
lastPage = realNumberOfPagesInDoc;
}
printingStatusDialog =
GetNewDialog(257, nil, (WindowPtr) -1);
for (copies = 1; copies <= numCopies; copies++) {
(**thePrRecHdl).prJob.pIdleProc =
CheckMyPrintDialogButton;
UseResFile(printmgrsResFile);
thePrPort = PrOpenDoc(thePrRecHdl, nil, nil);
pageNumber = firstPage;
while (pageNumber <= lastPage) {
PrOpenPage(thePrPort, nil);
DrawStuff((**thePrRecHdl).prInfo.rPage,
(GrafPtr)thePrPort, pageNumber);
PrClosePage(thePrPort);
++pageNumber;
}
PrCloseDoc(thePrPort);
}
if ((**thePrRecHdl).prJob.bJDocLoop == bSpoolLoop) {
PrPicFile(thePrRecHdl, nil, nil, nil, &theStatus);
}
}
}
PrClose();
DisposeHandle((Handle)thePrRecHdl);
DisposeDialog(printingStatusDialog);
SetPort(oldPort);
} /* PrintStuff */
VERSION 2: ERROR HANDLING ADDED
With code in the preliminary stage shown above, the flow of control is easy to follow.
After writing it, the programmer probably read through it and added some
error-handling code. Adding "if (error == noErr)" logic wasn't difficult, but it took
some thought to determine how to handle the cleanup and deal with the two loops. Some
more error-handling code may have been added after running the routine under
stressful conditions. Perhaps it was reviewed by lots of people before it went out as a
Technical Note. Here's the new version of the code (with the added error-handling code
shown in bold):
void PrintStuff(void)
GrafPtr oldPort;
short copies, firstPage, lastPage, numCopies,
printmgrsResFile, realNumberOfPagesInDoc,
pageNumber, printError;
DialogPtr printingStatusDialog;
THPrint thePrRecHdl;
TPPrPort thePrPort;
TPrStatus theStatus;
GetPort(&oldPort);
UnLoadTheWorld();
thePrRecHdl = (THPrint)NewHandle(sizeof(TPrint));
if (MemError() == noErr && thePrRecHdl != nil) {
PrOpen();
if (PrError() == noErr) {
printmgrsResFile = CurResFile();
PrintDefault(thePrRecHdl);
if (PrError() == noErr) {
if (PrStlDialog(thePrRecHdl)) {
realNumberOfPagesInDoc =
DetermineNumberOfPagesInDoc(
(**thePrRecHdl).prInfo.rPage);
if (PrJobDialog(thePrRecHdl)) {
numCopies = (**thePrRecHdl).prJob.iCopies;
firstPage = (**thePrRecHdl).prJob.iFstPage;
lastPage = (**thePrRecHdl).prJob.iLstPage;
(**thePrRecHdl).prJob.iFstPage = 1;
(**thePrRecHdl).prJob.iLstPage = 9999;
if (realNumberOfPagesInDoc < lastPage) {
lastPage = realNumberOfPagesInDoc;
}
printingStatusDialog =
GetNewDialog(257, nil, (WindowPtr) -1);
for (copies = 1; copies <= numCopies;
(**thePrRecHdl).prJob.pIdleProc =
CheckMyPrintDialogButton;
UseResFile(printmgrsResFile);
thePrPort =
PrOpenDoc(thePrRecHdl, nil, nil);
if (PrError() == noErr) {
pageNumber = firstPage;
while (pageNumber <= lastPage &&
PrOpenPage(thePrPort, nil);
if (PrError() == noErr) {
DrawStuff(
(**thePrRecHdl).prInfo.
rPage,
(GrafPtr)thePrPort,
pageNumber);
}
PrClosePage(thePrPort);
++pageNumber;
}
}
PrCloseDoc(thePrPort);
}
} else PrSetError(iPrAbort);
} else PrSetError(iPrAbort);
}