TweetFollow Us on Twitter

Error Handler Pascal

Volume Number: 15 (1999)
Issue Number: 9
Column Tag: Programming Techniques

A Simple Error Handler in Pascal

by Jim Phillips


One of the big differences between code you write for yourself and code you write for others is the quality of the runtime error handling. Your users will be much happier if you handle runtime errors gracefully. Gracefully means not destroying their data and preventing system crashes when errors occur that are nobody's fault. Your users have no recourse when your program misbehaves. They cannot debug or fix the code as you can. Simply put, part of being professional is handling runtime errors.

Unfortunately, writing error handling code is one of the more boring and tedious tasks that application programmers do. It therefore pays to simplify the writing of error handling code as much as possible by capturing repeated code in a separate module and reusing it throughout your application.

This has two additional benefits. First, it gives you an opportunity to put your application's mark on the error handling rather than defer to the system, compiler, or, possibly, third-party libraries. Second, it eliminates the need for a separate console window for debug messages during development. If you have gone to the trouble to create an attractive interface for displaying errors to the end user, it's surely good enough for you.

Typically, you may have to do four things to handle an error.

  • Check for the error.
  • Report the error to the end user.
  • Clean up.
  • Exit from the failed procedure or function.

The last two items may have to be repeated for each procedure or function in a chain of nested calls.

This article describes a module to organize and simplify the writing of error handling code for Macintosh applications. Since you have the source, you can easily adapt it to your application.

The source is presented in Apple's version of Pascal. However, the module can be implemented in C or C++. A version in C++ is available at <>.

Goals for the Error Handler

This section describes five goals for the error handler module.

First, the work horse error handling procedures should be short and easy to use consistent with performance and reliability. If these procedures are not easy to use, then they probably won't be.

Second, it should be easy for a client to write the error handling code without introducing programming errors. It is very annoying when a low frequency problem occurs and the user gets the wrong error message, or worse, no error message. Also the error handling code that executes after an error is detected should not itself cause crashes or destroy the user's data. This would be adding insult to injury.

Third, execution of the shipping code should be efficient in the absence of detected errors. However, it is not so important for the code that displays the error and cleans up the mess to be efficient. It's much more important that this code is correct and that it succeeds.

Fourth, the error handling module should be as complete as possible. We should have a convenient way to handle non-fatal, recoverable errors as well as fatal programming errors (bugs).

Memory mismanagement is a common type of error in programming languages without garbage collection. For this reason, the error handler should not try to allocate memory after an error is detected. This can be avoided by allocating and locking all memory required by the error handler early in the startup process.

In summary, our error handler module should have the following characteristics:

  • Implementation of error handling should be easy for the client.
  • Using the error handler should not be error prone.
  • Normal successful execution should be efficient.
  • It should handle everything from non-fatal errors to programming errors.
  • Error reporting should be safe even when memory is low.

Some of these goals are conflicting, so compromise will be necessary.

Handling Programming Errors in the Shipping Code

The standard thinking on debugging is that there should be two versions of your application code: the debug version and the shipping version. The debug version typically uses assertions and specialized testing code controlled by compiler directives. This extra debug code handles errors that would be fatal if they occurred in the shipping version. Errors that are nobody's fault, such as running out of memory, are handled gracefully whether they are fatal or nonfatal and this error handling is normally part of the shipping code. When all the bugs are found, the assertions and specialized testing code are removed for the shipping version. This also removes all overhead associated with the debug code, leaving only the no-fault error handling code. We have our cake and eat it too.

Or do we? As a developer, does anything bother you about this description? How about the part where we find all the bugs? And what is the consequence of removing all of our bug detection apparatus and leaving the end user to deal with bugs that escape to the shipping version?

Not everybody drops the ball in this respect. Occasionally, you see examples of programming errors that are handled in the shipping code. Here is one.

While using Symantec's C++ Compiler, I got the following error message: internal error 'file name' line number. The explanation for this error message in the Symantec C++ Compiler Guide is:

"This indicates a defect in the Symantec C++ compiler. Please contact Symantec technical support with details of this problem, including the filename and line number reported."

This is reporting a possible programming error in the compiler code. Not handling this error might have caused a crash and would have made it nearly impossible to find the bug. I followed up and reported this error and to my knowledge they fixed it.

Under "Error Message Types" in the Symantec Compiler manual there is further information about internal errors: "An assertion failure within the compiler generates this type of error ...". This got me thinking about intentionally leaving assertion-like statements in the shipping code.

The Trouble with Assertions

The reason we use assertions is to find bugs during development. Assertions are not supposed to be used to handle errors because they will be removed from the shipping code. Since they will be removed, we can use them freely without worrying about performance.

On the plus side, assertions are probably the simplest way to state an error condition. They completely hide the reporting, cleanup, and exiting steps of error handling. As such, they are very easy to use which means that they are more likely to be used.

However, assertions have two important problems: side effects and no protection in the shipping code. The first can mask bugs in the shipping code and the second makes it very hard to find the difficult bugs that escape the development process.

Avoiding side effects requires care on the part of the programmer, both in the implementation of Assert and in the use of Assert. Steve Maguire in "Writing Solid Code" describes why you probably do not want to write your own assertions. You have to be very careful that memory is not allocated or moved when an assertion is used. Otherwise, the shipping code executes differently from the debug code. For this reason, assertions in C/C++ are implemented as macros rather than functions. Even so, there is no way to have identical memory usage because the code itself is larger with assertions turned on.

When you use assertions, you have to avoid putting function calls as an argument to the assertion. When the assertion is removed for shipping these functions will not be executed, possibly introducing undetected bugs into the shipping code. For more information on correct use of assertions see Peter Lewis's excellent MacTech article "Using Assert()" (Lewis, 1997) and Steve Maguire's book "Writing Solid Code" (Maguire, 1993).

The worst thing about assertions is that they don't guarantee that your shipping code has no bugs. You can look for bugs, try to prevent them, and test for them, but you can't prove that you found them all. Therefore, it is possible for bugs to escape to the shipping code. And they do, don't they. Furthermore, these "shipping bugs" are more likely to be obscure and hard to find because they got through your careful development process. And since you've removed the assertions that would have flagged these bugs, ironically, the program is more likely to crash in the user's hands.

What can we do about shipping bugs? One approach is to leave a few assertion-like checks in the shipping code. They have the advantage that, by definition, they can't introduce side effects into the shipping code. And they protect the end user and may help you find bugs in the shipping code.

Error Checking Code Performance

The most direct way to accomplish the four error handling tasks is to write a procedure that takes three arguments: the boolean expression to be checked, the error message, and a cleanup procedure to be executed. If we consider performance in the normal successful case, only the boolean expression will be checked. Unfortunately, the overhead of a procedure call dwarfs the time required to check a boolean expression.

For example, consider the following two code snippets:

(a) HandleErrorIf(error <> noErr, message, CleanUpAndExit);

(b) if (error <> noErr) then
		HandleError(message, CleanUpAndExit);

Using CodeWarrior with debugging and optimization off, Code snippet (a) runs about 3 times slower than code snippet (b) when the string is passed by reference and about 15 times slower when the string is passed by value. This ratio will vary depending on the compiler and the language, but it is always significant because of the overhead of the procedure call. The down side of code snippet (b) is that it requires a little more text, so it is a little less convenient. However, in my judgement, the performance hit is just too great when there is no error. So we will use code snippet (b) as our model.

That being said, if the error check is a simple boolean expression, then our handling of error conditions is extremely cheap when no error occurs. The compiled code to check the error condition is at most a few instructions and it is, in any case, the minimum required to detect an error. There is just no excuse not to check error codes, for example.

Note that we can easily afford the procedure overhead after an error has occurred. Since the error handling procedures will be executed only a few times at most, the extra overhead of the procedure call will only take a fraction of a second.

You can have your cake and eat it too, if you are comfortable using macros. CodeWarrior lets you do macros in Pascal. C/C++ programmers will not hesitate, of course! Here is how you would implement the macro in Pascal:

{$DEFINEC ProgramErrorIf(condition, message, CleanUpAndExit)

	if (condition) then ProgramError(message, CleanUpAndExit)}

You can see that you are not actually saving that many keystrokes!

Workhorse Procedures: HandleError, LogError, and ProgramError

The prototypes for these procedures in increasing order of severity are:

procedure HandleError(message : Str255;
		procedure CleanUpAndExit);
procedure LogError(message : Str255;
		procedure CleanUpAndExit);
procedure ProgramError(message, procName, unitName : Str255);

Each of these three functions performs the last three tasks of handling an error listed in the introduction. The first task, checking the error condition, is always handled directly in the code for performance reasons.

The first procedure, HandleError, is intended for the end-user. It displays a modal "stop" alert dialog, cleans up any processes that are partially completed, sets any error codes, and exits from the failed procedure. The error message should contain what went wrong, why it went wrong, and suggestions for correcting the problem. It should be clear, brief, and complete. It should give information in terms that the average user can understand. There should be just one such dialog per error. Paige Parson's article "Guidelines for Effective Alerts" (Parsons, 1995) gives lots of great advice about the content of such dialogs.

The messages shown by HandleError should be easily localizable, so we will use string resources. We will also implicitly take advantage of Toolbox text utilities that will display messages correctly in many different languages.

From a programming perspective, there may be a chain of calls resulting in a failure in some low-level procedure. To recover, you need to cleanup and return from each procedure until you get back to the main event loop. There is usually an ideal procedure from which to show the error dialog, and this is not necessarily in the procedure where the error occurred. If you show the dialog at too low a level, your message is apt to be too technical and far removed from what the user was doing. If you show the message at too high a level, your message may be too vague; you may have lost critical details about the nature of the error. Choosing where is a judgement call, but there should be only one error dialog shown.

The second procedure, LogError, is intended for the developer or sophisticated user. It opens an error log file, writes the error message, closes the file, cleans up, sets error codes, and exits from the failed procedure. The number of error messages written to the error log file is limited and the file is rewritten each time the program is run. This prevents the accumulation of "garbage" files, either in the form of one large file or many small files, that the user may not even know are accumulating. The messages can be technical and in the developer's native language. They can report system errors or anything that the developer might find useful for debugging.

LogError is useful when an error is discovered deep in the bowels of the program. It is not appropriate to call HandleError because the program is at too low a level. But it is sometimes nice to know exactly what the first error was. LogError lets you record the error without interrupting the user with information that may not be useful to them, or worse, frighten them.

The third procedure, ProgramError, is intended strictly for the developer. You only call this procedure if a serious error has been detected and it is too dangerous for the program to continue or even clean up. The most important thing to do in this case is to report the error. ProgramError displays an alert dialog that describes the error, its location in the code, and then exits the program.

This scheme relies on the user to forward error information to the developer as in the Symantec "internal error". Perhaps a reward should be offered to users for help in reporting bugs. An announcement to this effect could be included in the alert dialog.

You should use ProgramError to at least check arguments that come from outside the module containing the procedure or function. The procedure or function cannot be expected to give correct results if its inputs are wrong. In other words, the bug lies outside the module; it is a client error. Now as the programmer of the module, you have chosen its scope to be intellectually manageable. You want to be able to debug the module in isolation. But if you do not check its inputs, you allow an upstream error to propagate and it may not be caught by other sanity checks further downstream in your procedure or function. This couples modules together, violates your own decomposition, and makes it so you can't debug the module in isolation.

Sometimes it is too expensive to check inputs to your module in the shipping code. In that case, at least do inexpensive sanity checks. It is very important to start off on the right foot.

Using ProgramError to check internal constraints of your module is really looking for bugs within the module. Here, there will be a tradeoff between the cost of checking in the absence of error and the value of catching bugs in the shipping code. In some cases, the cost of checking can ruin the performance of an algorithm. You should use ProgramError in combination with assertions and/or specialized debugging code controlled by compiler directives.

Using the Error Handler: MyApplication Example

The error handler code makes use of Pascal's nested procedures and the standard "exit" procedure. For each procedure or function where HandleError or LogError may be called, the programmer writes a nested procedure that cleans up anything that was done before the error was detected. This nested cleanup procedure then sets any return results and exits from the outer procedure.

For example, let's say that your application opens a document file and loads the data into a buffer. We'll simulate this with a function that allocates two handles of different sizes. The interface for our utilities unit (MyUtilities.p) defines the file data structure that contains the file spec and two buffers and the open file prototype.

unit MyUtilities;


		tMyFile = record
			smallBuffer: Handle;
			largeBuffer: Handle;
			fileSpec: FSSpec;

	function MyOpenFile (fileName: Str255;
			var fileData: tMyFile): OSErr;

Now we implement the MyUtilities unit.

First, we import the ErrorHandler unit, declare private constants and types, and write a private helper function (ErrStr). This helper function links local ordinal constants to an error string resource that contains the actual error messages.


		UnitName = 'MyUtilities';

		oErrorString = (UnknownError,
			FileBufferErr1, { Couldn't open the file "filename". }
			FileBufferErr2	 { because ...}

	function ErrStr (errorNumber: oErrorString): Str255;
	ErrStr := GetErrorString(ord(errorNumber), uMyUtilities);

Now we can implement a private helper function that allocates the two buffers.

	function AllocateHandles (
			var largeHandle, smallHandle: handle;
			size: integer): OSErr;

		ProcName = 'AllocateHandles';

		SmallHandleError = 
		'Small handle allocation failed in AllocateHandles.';
		LargeHandleError = 
		'Large handle allocation failed in AllocateHandles.';

		SizeError = 
		'Trying to allocate handles with negative size.';

		error: OSErr;

		procedure CleanupAndExit;
		AllocateHandles := memFullErr;

		if (largeHandle <> nil) then
			largeHandle := nil;
		if (smallHandle <> nil) then
			smallHandle := nil;



	if (size < 0) then
		ProgramError(SizeError, ProcName, UnitName);

	largeHandle := nil;
	smallHandle := nil;

	largeHandle := NewHandle(2 * size);
	if (largeHandle = nil) then
		LogError(LargeHandleError, CleanUpAndExit);

	{ Next line is commented out to simulate failure. } 
	{ smallHandle := NewHandle(size); }

	if (smallHandle = nil) then
		LogError(SmallHandleError, CleanUpAndExit);

	AllocateHandles := noErr;

This function has full error checking. The small handle allocation is commented out to simulate an allocation failure. This function is called from MyOpenFile, which in turn calls HandleError if it fails. It is appropriate for MyOpenFile to call HandleError because the file name should be part of the error message and AllocateHandles doesn't have access to it.

It is good practice to check each memory allocation immediately after trying to allocate. In the example above, a large allocation precedes a small allocation. It's entirely possible that the large allocation can fail but the small allocation succeeds. This is why you can't simply check the last allocation in a series of allocations. Also, if you use MemError to check an allocation, you have to check it immediately because its result is changed after each new allocation.

Notice how the exit statement in the CleanUpAndExit procedure gets us all the way out of AllocateHandles, not just the nested CleanUpAndExit procedure. Furthermore, this works when CleanUpAndExit is called from inside HandleError or LogError. This feature of Apple's Pascal lets us elegantly exit AllocateHandles from the nested procedure CleanUpAndExit so we don't have to clutter up the main code with explicit exit statements.

The AllocateHandles procedure also shows an example of using the ProgramError procedure. Notice how the arguments appear in order of increasing scope (message, procedure, unit). This helps you to remember the order. This is important because with the arguments all being the same type (Str255), you can mix up the order and the error will not be caught at compile time. On the other hand, if you forget to declare the UnitName or ProcName arguments, the compiler will catch it.

Finally, we write the public open file procedure. This procedure calls the private helper function, AllocateHandles, to allocate the two buffers. During the file open operation there are two classes of errors that might occur: file I/O errors and memory allocation errors. The user definitely needs to know which type of error has occurred, but they also need to know the file name. The exact details of why a memory allocation failed may not be useful to the end user, so we silently log the error, clean up, then handle the error at the level of the file open procedure where we have access to the file name. The MyOpenFile source is shown below.

function MyOpenFile (fileName: Str255;
		var fileData: tMYFile): OSErr;
		kSmallBufferSize = 2000;

		error: OSErr;

		function FileBufferErr: Str255;
				errorString: Str255;
		errorString := ErrStr(FileBufferErr1);
		AppendQuote(errorString, fileName);
		SafeAppend(errorString, ErrStr(FileBufferErr2));
		FileBufferErr := errorString;

		procedure CleanupAndExit;
		MyOpenFile := error;
			{ Put clean up here. }

error := AllocateHandles(fileData.largeBuffer,
		fileData.smallBuffer, kSmallBufferSize);
if (error <> noErr) then
	HandleError(FileBufferErr, CleanUpAndExit);

MyOpenFile := noErr;

Aside: Apple Pascal "Exit" Procedure

The Object Pascal "Exit" procedure, available in Think Pascal and CodeWarrior Pascal, is an extension to Standard Pascal. However, its functionality can always be implemented using a goto statement from Standard Pascal, but the code is much less readable. In combination with nested procedures and functions, it is very useful for implementing error handling. This section describes its history and rationale.

Standard Pascal has only three iteration statements: the for statement, the while statement, and the repeat statement. The for statement is intended to be used only when you know exactly how many times you will iterate. The while and the repeat statements iterate a variable number of times but show their exit condition(s) at the start or the end of the enclosed iteration block. These are the natural locations to show exit conditions.

It's important for readability to be able to quickly determine the exit conditions of an iteration. If it's possible to have exit conditions in the interior of the iteration block, then the reader has to search through the block to understand how the iteration works. However, there are times when the most elegant thing to do is to exit part of the way through an iteration or exit from more than one nested block. So Standard Pascal has the goto statement to handle all these unusual situations that can't be handled gracefully using the three iteration statements. The goto lets you exit from the interior of a block or procedure to any outer block or procedure, so it works in conjunction with nested blocks, procedures, and functions.

In Apple's Pascal, the "Exit" statement takes a single argument, which is the name of a procedure or function from which to exit. This argument is only useful when you have nested procedures; you can exit immediately to the scope that you desire. For example, if procedure A contains procedure B and procedure B contains procedure C, you can exit directly from C to A. This is very useful for implementing an error handler module as we have seen.

This form of the exit statement dates back to UCSD Pascal, which was developed in the late 1970's. UCSD Pascal showed that efficient Pascal compilers could be implemented on microcomputers. It is one of the primary reasons Pascal became popular in the first place. Many of its extensions were carried on into Apple's Pascal and Borland's Turbo Pascal.

HandleError Implementation

procedure HandleError (errorMessage: Str255;
		procedure CleanUpAndExit);
if (DisplayingError) then 
	DisplayingError := false;


The display of the error dialog is protected by a public boolean variable: DisplayingError. DisplayingError is initialized to true and then is set to false only when the error message is displayed. The client can reset it by assigning it to true. This insures that only one error dialog is displayed until the client sets DisplayingError to true. The programmer can then freely use HandleError without having to know if it is called above or below the current procedure.

ConstructErrorText checks and prepares the message for the dialog box. It replaces the empty string with the "Unknown Error" string. It can be used to add titles and line breaks, if desired.

DisplayError shows the error dialog and waits until the user selects the OK button. It should work even when memory is low because it may be reporting a memory allocation failure! Its implementation will be discussed in a later section.

Memory Management Strategy

All three of HandleError, LogError, and ProgramError should work in low memory conditions. It's very important that the user knows what went wrong. It is not acceptable to "unexpectedly quit".

Whenever possible, our strategy will be to preallocate the memory we need. The string list resources that contain the error messages should be marked "preload" and "locked". They will then be automatically loaded into memory at startup. The ErrorHandler unit will be loaded into memory when you call InitErrorHandler. If your are developing for 68k, do not call Unloadseg on the error handler unit.

For the dialog, we will allocate a handle at startup large enough for the dialog and anything else needed to display the alert. When it comes time to show the alert, we will free the handle, show the alert, and then reallocate the handle. We want to use a handle so that we do not fragment memory when we do the reallocation.

Finally, we will store important state information in static variables so that we do not have to call procedures that may allocate memory to get this information after an error has occurred. This includes information about the log file and the reserve memory handle.

DisplayError Implementation

This procedure needs to display a standard "stop" alert with the error message. This message may be from 1 to 255 characters in length. A dialog large enough to hold a 255 character string will look unprofessional with only a few words in it. Our strategies range from always displaying the same large dialog to dynamically sizing the dialog for each message. Dynamic sizing is complicated by the possibility that the message may be in other languages, some of which are so large that they require two bytes per character (Japanese, Chinese) and some of which are read from right-to-left (Arabic, Hebrew).

The approach taken here is to determine how many lines we need and adjust the height of a default dialog which is stored as a resource. The first step is to count the number of lines required to fit the message within the width of our default dialog text field after proper line breaking. Multiplying the number of lines times the line height gives us the height of the text field. If it is smaller than the height of our default text field, then we simply display the error. If it is less than some reasonable maximum height, then we adjust the height of the dialog accordingly. If it is larger than the maximum height, then we let the string run off the end of our largest allowed text field. Don't worry, the dialog manager will clip the text to the available text field area.

The utility function CountLines calls the Toolbox routine StyledLineBreak to compute the number of lines that the dialog manager will use to display the message in the system font.

The source for DisplayError is shown below.

procedure DisplayError(errorMessage: Str255);
if (sReservedSpace <> nil) then
	sReservedSpace := nil;


	sReservedSpace := NewHandle(DisplayBytes);
	if (sReservedSpace = nil) then

DisplayError uses one static variable: sReservedSpace. The identifier is prefixed by a small "s" for "static". sReservedSpace is initialized in InitErrorHandler (to be discussed later).

If our reserve memory is not available, then something is seriously wrong with out memory management. The error handler has probably already displayed an error, so we halt.

ErrorAlert Implementation

ErrorAlert is implemented using ModalDialog as follows:

procedure ErrorAlert (errorMessage: Str255);
		savePort: GrafPtr;
		dialogFontInfo: FontInfo;
		mainScreen: GDHandle;
		lines: integer;
		lineHeight: integer;

		heightChange: integer;
		textHeightPixels: integer;
		textWidthPixels: integer;
		windowWidth: integer;
		windowHeight: integer;
		newTextHeight: integer;

		theDialog: DialogPtr;
		itemHandle: Handle;
		itemType: integer;

		textHandle: Handle;
		textRect: Rect;
		buttonHandle: ControlHandle;
		buttonRect: Rect;
		windowHGlobal: integer;
		windowVGlobal: integer;

		itemHit: integer;
	{ Deactivate your top window here. }

theDialog := GetNewDialog(kErrorAlertID, nil, Pointer(-1));

	{ Make sure the dialog's GrafPort is set to the System font and style. }



	{ Get the line height (in pixels) of the dialog's font. }

with dialogFontInfo do
	lineHeight := ascent + descent + leading;

	{ Get the size of the dialog. }

with theDialog^.portRect do
	windowWidth := right - left;
	windowHeight := bottom - top;

	{ Get the size of the text field. }

GetDItem(theDialog, kErrorTextItem, itemType, textHandle,
with textRect do
	textHeightPixels := bottom - top;
	textWidthPixels := right - left;

lines := CountLines(errorMessage, textWidthPixels,

newTextHeight := lines * lineHeight;
if (newTextHeight > kTextHeightMax) then
	newTextHeight := kTextHeightMax;

heightChange := newTextHeight - textHeightPixels;

if (heightChange > 0) then
		{ Increase the size of the dialog. }
	windowHeight := windowHeight + heightChange;
	SizeWindow(theDialog, windowWidth, windowHeight, true);

		{ Move the OK button down. }

	GetDItem(theDialog, kErrorOKItem, itemType, itemHandle,
	buttonHandle := ControlHandle(itemHandle);
	OffsetRect(buttonRect, 0, heightChange);
	with buttonRect do
		MoveControl(buttonHandle, left, top);
	SetDItem(theDialog, kErrorOKItem, itemType, itemHandle,

		{ Extend the bottom of the text field. }

	textRect.bottom := textRect.bottom + heightChange;
	SetDItem(theDialog, kErrorTextItem, statText, textHandle,

SetDialogItemText(textHandle, errorMessage);

	{ Center the dialog on the main screen. }

mainScreen := GetMainDevice;
with mainScreen^^.gdRect do
	windowHGlobal := (left + right - windowWidth) div 2;
	windowVGlobal := (top + bottom - windowHeight) div 2;
MoveWindow(theDialog, windowHGlobal, windowVGlobal, true);


	ModalDialog(nil, itemHit);
until (itemHit = kErrorOKItem);



This code basically creates the specified dialog, adjusts the size of the dialog to contain the error message, replaces the static text with the error message, beeps, shows and handles the dialog, then destroys the dialog. The dialog contains just three items: the OK button, the stop icon, and the static text field and they should be numbered in that order. Note that the static text field should be enabled.

According to Inside Macintosh: Macintosh Toolbox Essentials (P. 6-64) you will need to deactivate your top window using whatever window management scheme you have implemented. This is because modal dialog traps all events once you call it, including deactivate events.

CountLines Implementation

If you want to do your own line breaks, or, as here, simply count line breaks, you will need to learn about the Toolbox routine StyledLineBreak. This magical routine will correctly break lines in 27 different writing systems (Guide to Macintosh Software Localization). All of these writing systems can be read from left-to-right or right-to-left except for one: Mongolian. For just counting lines, we don't care whether it's left-to-right or right-to-left. However, Mongolian must be read from top-to-bottom, then left-to-right. This means CountLines will not work properly for Mongolian (26 out of 27 isn't bad). Here is the source.

function CountLines (theText: Str255;
		fieldWidthPixels: integer;
		theGrafPort: GrafPtr): integer;
		lineCount: integer;
		lineStart: LongInt;
		textPtr: Ptr;
		lineBytes: LongInt;
		widthPixels: Fixed;
		linePixels: Fixed;
		breakBytes: LongInt;
		breakCode: StyledLineBreakCode;

		savePort: GrafPtr;
if (Length(theText) = 0) then
	CountLines := 1;


widthPixels := Long2Fix(LongInt(fieldWidthPixels)); { FixMath.p }
lineCount := 0;
lineStart := 1;
lineBytes := Length(theText);

	lineCount := lineCount + 1;
	linePixels := widthPixels;
	breakBytes := 1;
	textPtr := @theText[lineStart];

	breakCode := StyledLineBreak(textPtr, lineBytes, 0, 
			lineBytes, 0, linePixels, breakBytes);

	lineStart := lineStart + breakBytes;
	lineBytes := lineBytes - breakBytes;
until (breakCode = smBreakOverflow);


CountLines := lineCount;

CountLines computes the number of lines that will be required by the dialog manager to fit in a text field of a specified width in pixels using the system font. The hard work is done by StyledLineBreak. Since the dialog manager uses StyledLineBreak, you should get exactly the number of lines that CountLines reports when you actually show the dialog. Note that you need to include FixMath.p in your project to convert the integer field width to the Fixed data type.

Using StyledLineBreak means that when it comes time to localize your error messages, all you have to do is edit the string resources (assuming you know the other language), and not fool around with line breaks in custom dialog boxes.

For a more general treatment of fitting text into dialog boxes see Bryan Ressler's excellent article "The TextBox You've Always Wanted" (Ressler, 1992).

LogError Implementation

LogError's job is to write the specified error message to an error log file in the directory where your application is. The volume and folder is determined and saved when the ErrorHandler unit is initialized (InitErrorHandler). If the file doesn't exist when it comes time to write an error message, LogError creates it.

This version creates a read-only SimpleText file. The sophisticated user or you can simply double-click it to read the errors. Since the file is read-only, the modification date gives the time the last error was written. You could write other information at startup like the date, the system version, etc. You could also write the date and time before each error message, but I have chosen to keep it simple here.

Even though this is inefficient, we open and close the file for each error message. We even flush the volume to make sure that the changed directory data structure is written to disk right after writing the message. The reason is that this might turn out to be the last chance to report an error before the application crashes. Okay, you can call me paranoid. Here's the code.

procedure LogError (errorMessage: Str255;
		procedure CleanUpAndExit);
		kReadOnly = 'ttro'; { read only Simple Text file }
		kSimpleText = 'ttxt';

		error: OSErr;
		logFileSpec: FSSpec;
		refNum: integer;
		dividend: integer;
		digits: integer;
		theText: Str255;
		numBytes: Longint;

if (sLogErrorCount < kMaxLogErrors) then
	sLogErrorCount := sLogErrorCount + 1;
	refNum := 0;

	error := FSMakeFSSpec(sAppVRefNum, sAppDirID, 
			sLogFileName, logFileSpec);

	if (error = fnfErr) then	{ File doesn't exist; }
													{ create an empty one. }
		error := FSpCreate(logFileSpec, 
				kSimpleText, kReadOnly, smSystemScript);

	if (error = noErr) then { The file exists; open it. }
		error := FSpOpenDF(logFileSpec, fsRdWrPerm, refNum);

	if (error = noErr) then
		if (sLogErrorCount = 1) then { Overwrite the old file. }
			error := SetEOF(refNum, 0);

	if (error = noErr) then
		error := SetFPos(refNum, fsFromLEOF, 0);

	if (error = noErr) then
		digits := 0;
		dividend := sLogErrorCount;
		while (dividend > 0) do
			dividend := dividend div 10;
			digits := digits + 1;

		theText := Concat(StringOf(
				sLogErrorCount : digits), '. ');

		SafeAppend(theText, errorMessage);
		SafeAppend(theText, returnChar);
		SafeAppend(theText, returnChar);

		numBytes := Length(theText);
		error := FSWrite(refNum, numBytes, @theText[1]);

	if (refNum > 0) then
		error := FSClose(refNum);
		refNum := 0;
		error := FlushVol(nil, logFileSpec.vRefNum);


Note that we don't attempt to call HandleError if any of the file operations fail. It would be inappropriate to notify the user about the failure of an operation that they don't know about and didn't request.

ProgramError Implementation

ProgramError constructs a message to tell the developer what the error is and where it occurred in the code. This is similar to an assertion, but it is part of the shipping code.

procedure ProgramError (errorMessage, procName, 
		unitName: Str255);
DisplayError(LastWords(errorMessage, procName, unitName));

The procName and unitName arguments are typically local string constants. LastWords basically adds titles and line breaks for the procName and unitName strings.

function LastWords (errorMessage, 
		procName, unitName: Str255):Str255;
		suffix: Str255;
		temporaryString: Str255;
		excessCharacters: integer;
		prefixLength: integer;
		theLastWords: Str255;
if (errorMessage = '') then
	errorMessage := ErrStr(kUnknownError);
if (procName = '') then
	procName := ErrStr(kUnknown);
if (unitName = '') then
	unitName := ErrStr(kUnknown);

theLastWords := ErrStr(kFatalTitle);

prefixLength := Length(theLastWords);

suffix := Concat(returnChar, returnChar, ErrStr(kProcTitle));
SafeAppend(suffix, procName);
temporaryString := Concat(returnChar, returnChar,
SafeAppend(suffix, temporaryString);
SafeAppend(suffix, unitName);

excessCharacters := prefixLength + Length(suffix) - 255;
if (excessCharacters > 0) then
	TrimStringTail(errorMessage, excessCharacters);

SafeAppend(theLastWords, errorMessage);
SafeAppend(theLastWords, suffix);

LastWords := theLastWords;

The SafeAppend and TrimStringTail string utilities are part of a string utilities unit. They will not be described but are available on the Mac Tech ftp site at <>.

String Utilities

Much of the code in error handling is just string manipulation. We need to get the correct string from a resource, possibly append a quoted string that the end user understands, and put strings together without overrunning allocated memory. For these three things, I provide GetErrorString, AppendQuote, and SafeAppend.

GetErrorString makes it easy for you, the client, to map resource strings to private ordinal constants. Ordinal constants are safer than integer constants because range errors are caught at compile time. The problem is that these ordinal constants should be hidden in the implementation section of the unit where they are used. This prevents outside access and avoids name conflicts, but it also hides them from the ErrorHandler unit.

The idiom for connecting these private ordinal constants to the actual resource strings is as follows. Create a unit called ErrorDefinitions that declares an ordinal type that maps ordinal constants to a series of string list resources. Prefix each constant with a lower case "u" (short for unit), for example, "uMyUtilities". Provide a function GetErrorStringResourceID that maps each ordinal constant to its resource ID. The most direct way to do this is to use a case statement.

unit ErrorDefinitions;

		ProgramName = 'MyApplication';

		oUnitID = (

	function GetErrorStringResourceID (
			unitID: oUnitID): integer;


	function GetErrorStringResourceID (
			unitID: oUnitID): integer;
	case unitID of
		GetErrorStringResourceID := 400;
		GetErrorStringResourceID := 0;

Next create a private function that maps your private ordinal type to a string in the string list resource corresponding to this unit. This private function uses GetErrorString to do the bookkeeping. Typically, the name of this function is ErrStr to keep it short so that the HandleError call can be done on one line. For the MyApplication example, see the code at the top of the implementation in the section "Using the Error Handler: MyApplication Example".

We use the Pascal built in function "ord" to convert the ordinal constant to an integer for GetErrorString. The ordinal type should have the same number and order as the error strings in your string resource list except for the first element, which is given the name "UnknownError". The ord of the first element of an ordinal type is "0" but the first string in a string resource list is number "1". Ordinarily, the UnknownError constant will not be used.

The ordinal constant identifiers, such as "FileBufferErr1", should be fairly verbose, since they substitute for the error message in your code. On the other hand, they shouldn't be so long that we need to use two lines of code to call HandleError.

The ErrorHandler function GetErrorString gets the specified error string from the MyUtilities resource string list using the toolbox routine GetIndString. If you have forgotten to add the unit identifier uMyUtilities to ErrorDefinitions.p, this will be caught at compile time when you try to compile your local ErrStr. If you have forgotten to create the resource string list, this will be caught at startup by the procedure CheckErrorStrings, which tries to open all of the resource error, string lists you have specified in ErrorDefinitions. The code for GetErrorString appears below.

function GetErrorString (errorNumber: integer; 
		unitID: oUnitID): Str255;
		theErrorMessage: Str255;
		stringResourceID: integer;
if (errorNumber = 0) then
	GetIndString(theErrorMessage, kErrorStringsID,
			ord(kUnknownError) + 1)
	stringResourceID := GetErrorStringResourceID(unitID);
	if (stringResourceID > 0) then
		GetIndString(theErrorMessage, stringResourceID,
		GetIndString(theErrorMessage, kErrorStringsID,
				ord(MissingErrorStringListErr) + 1);

GetErrorString := theErrorMessage;

If you forget to add the error string to the resource string list, GetIndString will return the empty string and, unfortunately, this will occur at runtime. If you use GetErrorString to pass the string to LogError, it will simply show the empty string or the unknown error string. To help avoid this type of error, the procedure TestUnitErrors is provided. TestUnitErrors displays each message in a specified unit.

Sometimes the error message cannot be stored in advance and must be constructed on the fly. For example, in MyOpenFile, we want to include the file name as part of the message. In this case we can create a nested function that returns the constructed error message (see the function FileBufferErr in the section "Using the Error Handler: MyApplication Example").

In this example, FileBufferErr constructs the following message:

MyApplication could not open the file "MyFile" because there is not enough memory to allocate the required file buffers.

Try closing MyApplication windows, quitting other applications, or giving MyApplication more memory using the Get Info dialog.

"MyFile" is the file name used by our test program. FileBufferErr1 contains the message before the quoted file name. FileBufferErr2 contains the rest of the message.

AppendQuote is a helper function in the ErrorHandler unit to put the proper curly double quotes around a string that you want to append to another string. SafeAppend concatenates two strings using the first string's storage. If the second string is too long to fit in the first string's remaining storage (maximum 255 bytes), then the second string is truncated to fit. AppendQuote uses SafeAppend as follows:

procedure AppendQuote (var message: Str255; 
		theQuote: Str255);
		LeftQuotes = chr(210);
		RightQuotes = chr(211);
SafeAppend(message, LeftQuotes);
SafeAppend(message, theQuote);
SafeAppend(message, RightQuotes);

Odds and Ends

This section cleans up this article by describing the ErrorHandler unit private constants, types, and variables. It also documents the InitErrorHandler function to be called at startup of the program.

The constant section is shown below.

The first constant, DisplayBytes, is the number of bytes reserved for displaying the error dialog. This includes any additional heap space required by the system to display the dialog.

The second constant is the maximum number of errors in the error log. It should be less than about 128 so that even if the strings are full (255 bytes each), the total space cannot exceed 32,767 which is (still) the limit for SimpleText.

The next five items refer to the error dialog. The first two constants are the resource ID's of the string list used by the error handler and the error dialog, respectively. The next two constants are the dialog item numbers of the OK button and the text field where the error message will be displayed. The next item is the maximum allowed height of the text field in pixels. The width is always the same but the height varies.

The last three constants are self-explanatory.

	kDisplayBytes = 5 * 1024; { heap space for error dialog }
	kMaxLogErrors = 100;

	kErrorStringsID = 200;	{ resource ID of ErrorHandler strings }

	kErrorAlertID = 401;
	kErrorOKItem = 1;
	kErrorTextItem = 3;
	kTextHeightMax = 200;
	UnitName = 'ErrorHandler';

	returnChar = chr(13);
	tabChar = chr(9);

The Error Handler uses ordinal types exactly like the user's units. It uses the oErrorString ordinal type defined as follows:

	oErrorString = (
		kUnknownError,	{ "Unknown error." }
		kUnknown,			{ "Unknown." }
		kFatalTitle,		{ "Programming Error: " }
		kProcTitle,		{ "Where: " }
		kUnitTitle,		{ "Unit: " }
			{ "An error string list resource is missing." }

The messages associated with these constants are found in the "Error Handler Strings" resource of type "STR#" in the file "ErrorHandler.rsrc". The local ErrStr function is slightly different than the client's local ErrStr function. The difference between the ordinal constant offset (0) and the string list offset (1) is hidden for the client by GetErrorString. This explains the "+ 1" in the ErrorHandler module's ErrStr.

function ErrStr (errorNumber: oErrorString): Str255;
		theErrorMessage: Str255;
GetIndString(theErrorMessage, kErrorStringsID, 
		ord(errorNumber) + 1);
ErrStr := theErrorMessage;

The var (or variable) section of the implementation contains static variables (prefix "s") that are allocated at startup in the global storage area. This means they will likely be available when an error message needs to be displayed. Most of these variables have already been discussed.

	sLogErrorCount: integer;
	sLogFileName: Str255;
	sReservedSpace: Handle;
	sAppVRefNum: integer;
	sAppDirID: Longint;

The InitErrorHandler function allocates the reserve memory required to display an error as well as obtaining information about the application's volume and folder.

function InitErrorHandler: boolean;
		error: OSErr;
DisplayingError := true;
sLogErrorCount := 0;

sLogFileName := ProgramName;
SafeAppend(sLogFileName, '.log');

error := HGetVol(nil, sAppVRefNum, sAppDirID);

sReservedSpace := nil;
sReservedSpace := NewHandleClear(DisplayBytes);

InitErrorHandler := (error = noErr) and 
		(sReservedSpace <> nil);

DisplayingError gives the client control over when the error handler is reset to fire again. It is a flag that is intended to prevent multiple messages for the same error. You typically assign it to true in your main event loop.

The following procedures and functions have not been described explicitly but are available at the Mac Tech web site: SafeAppend, TrimStringTail, FreeErrorHandler, ConstructErrorText, CheckErrorStrings, TestUnitErrors, and TestAllErrors.


I have presented a simple error handler module in Apple's version of Pascal. It provides general utility procedures for displaying error messages, executing client defined clean up procedures, and exiting the failed procedure. The three workhorse procedures: HandleError, LogError, and ProgramError, give you the flexibility to handle errors ranging from nonfatal errors, such as memory exhaustion, to fatal errors caused by software bugs. Care has been taken to make these error handling procedures work even when the application is out of memory. In addition, there are string-handling utilities that let you prepare messages for dialog boxes and extract error messages from string resources so you can easily localize your application. This error handler module, or something like it, is essential to make your application professional.


  • Apple Computer. Guide to Macintosh Software Localization, Addison-Wesley Publishing Company, 1992.
  • Apple Computer. Inside Macintosh: Macintosh Toolbox Essentials, Addison-Wesley Publishing Company, 1992.
  • Lewis, Peter N. "Using Assert()", MacTech, December 97.
  • Maguire, Steve. Writing Solid Code, Microsoft Press, 1993.
  • Parsons, Paige K. "Guidelines for Effective Alerts", develop, Issue 24, December 1995.
  • Ressler, Bryan K. "The Textbox You've Always Wanted", develop, Issue 9, Winter 92.

Jim Phillips has been programming in Pascal on the Macintosh since 1986. He is an aeronautical engineer by training, but he would rather write programs to do engineering than do engineering. Send comments to


Community Search:
MacTech Search:

Software Updates via MacUpdate

Garmin Express - Manage your Gar...
Garmin Express is your essential tool for managing your Garmin devices. Update maps, golf courses and device software. You can even register your device. Update maps Update software Register your... Read more
ClipGrab 3.8.12 - Download videos from Y...
ClipGrab is a free downloader and converter for YouTube, Vimeo, Facebook and many other online video sites. It converts downloaded videos to MPEG4, MP3 or other formats in just one easy step Version... Read more
VMware Fusion 11.5.5 - Run Windows apps...
VMware Fusion and Fusion Pro - virtualization software for running Windows, Linux, and other systems on a Mac without rebooting. The latest version includes full support for Windows 10, macOS Mojave... Read more
Civilization VI 1.3.0 - Next iteration o...
Civilization® VI is the award-winning experience. Expand your empire across the map, advance your culture, and compete against history’s greatest leaders to build a civilization that will stand the... Read more
Google Earth - View and contr...
Google Earth gives you a wealth of imagery and geographic information. Explore destinations like Maui and Paris, or browse content from Wikipedia, National Geographic, and more. Google Earth combines... Read more
Corel Painter - Digital art s...
Corel Painter lets you advance your digital art style with painted textures, subtle glazing brushwork, interactive gradients, and realistic Natural-Media. Easily transition from traditional to... Read more
iTubeDownloader 6.5.19 - Easily download...
iTubeDownloader is a powerful-yet-simple YouTube downloader for the masses. Because it contains a proprietary browser, you can browse YouTube like you normally would. When you see something you want... Read more
OmniFocus 3.8 - GTD task manager with iO...
OmniFocus is an organizer app. It uses projects to organize tasks naturally, and then add tags to organize across projects. Easily enter tasks when you’re on the go, and process them when you have... Read more
Hazel 4.4.5 - Create rules for organizin...
Hazel is your personal housekeeper, organizing and cleaning folders based on rules you define. Hazel can also manage your trash and uninstall your applications. Organize your files using a familiar... Read more
Macs Fan Control 1.5.7 - Monitor and con...
Macs Fan Control allows you to monitor and control almost any aspect of your computer's fans, with support for controlling fan speed, temperature sensors pane, menu-bar icon, and autostart with... Read more

Latest Forum Discussions

See All

Dungonian is a card-based dungeon crawle...
Dungonian is a card-based dungeon crawler from developer SandFish Games that only recently launched as a free-to-play title. It offers an extensive roster of playable heroes to collect and enemies to take down, and it's available right now for iOS... | Read more »
Steam Link Spotlight - Signs of the Sojo...
Steam Link Spotlight is a feature where we look at PC games that play exceptionally well using the Steam Link app. Our last entry was XCOM: Chimera Squad. Read about how it plays using Steam Link's new mouse and keyboard support over here. | Read more »
Steampunk Tower 2, DreamGate's sequ...
Steampunk Tower 2 is a DreamGate's follow up to their previous tower defence game. It's available now for both iOS and Android as a free-to-play title and will see players defending their lone base by kitting it out with a variety of turrets. [... | Read more »
Clash Royale: The Road to Legendary Aren...
Supercell recently celebrated its 10th anniversary and their best title, Clash Royale, is as good as it's ever been. Even for lapsed players, returning to the game is as easy as can be. If you want to join us in picking the game back up, we've put... | Read more »
Pokemon Go Fest 2020 will be a virtual e...
Niantic has announced that Pokemon Go Fest will still take place this year although understandably it won't be a physical event. Instead, it will become a virtual celebration and is set to be held on 25th and 26th July. [Read more] | Read more »
Marvel Future Fight's major May upd...
Marvel Future Fight's latest update has now landed, and it sounds like a big one. The focus this time around is on Marvel's Guardians of the Galaxy, and it introduces all-new characters, quests, and uniforms for players to collect. [Read more] | Read more »
SINoALICE, Yoko Taro and Pokelabo's...
Yoko Taro and developer Pokelabo's SINoALICE has now opened for pre-registration over on the App Store. It's already amassed 1.5 million Android pre-registrations, and it's currently slated to launch on July 1st. [Read more] | Read more »
Masketeers: Idle Has Fallen's lates...
Masketeers: Idle Has Fallen is the latest endeavour from Appxplore, the folks behind Crab War, Thor: War of Tapnarok and Light A Way. It's an idle RPG that's currently available for Android in Early Access and will head to iOS at a later date. [... | Read more »
Evil Hunter Tycoon celebrates 2 million...
Evil Hunter Tycoon has proved to be quite the hit since launching back in March, with its most recent milestone being 2 million downloads. To celebrate the achievement, developer Super Planet has released a new updated called Darkness' Front Yard... | Read more »
Peak's Edge is an intriguing roguel...
Peak's Edge is an upcoming roguelike puzzle game from developer Kenny Sun that's heading for both iOS and Android on June 4th as a free-to-play title. It will see players rolling a pyramid shape through a variety of different levels. [Read more] | Read more »

Price Scanner via

Sale! $200 off on select 2020 13″ MacBook Pro...
Amazon has select 2020 13″ MacBook Pro configurations on sale today for $200 off Apple’s MSRP. Shipping is free. Be sure to purchase the MacBook Pro from Amazon, rather than a third-party seller, and... Read more
June 1 only: $100 off Apple’s iPhones at Boos...
Boost Mobile is offering Apple iPhone 11, 11 Pro, and iPhone 11 Pro Max models for $100 off MSRP with service. Their discount reduces the cost of an iPhone 11/64GB to $599, iPhone 11 Pro to $899 for... Read more
Sams Club Sales Event: $100 off every Apple W...
Sams Club is discounting all Apple Watch Series 5 models by $100 off Apple’s MSRP through June 3, 2020. Choose free shipping or free local store pickup (if available). Sale prices for online orders... Read more
New 16″ MacBook Pros now on sale for up to $2...
Apple reseller DataVision is now offering new 16″ Apple MacBook Pros for up to $255 off MSRP, each including free shipping. Prices start at $2194. DataVision charges sales tax for NY, NJ, PA, and CA... Read more
Apple now offering Certified Refurbished iPho...
Apple is now offering Certified Refurbished iPhone Xr models in the refurbished section of their online store starting at $499. Each iPhone comes with Apple’s standard one-year warranty, ships free,... Read more
Sale! Get a 10.2″ 32GB WiFi iPad for only $27...
Walmart has new 10.2″ 32GB WiFi iPads on sale for $50 off Apple’s MSRP, only $279. These are the same iPads sold by Apple in their retail and online stores. Be sure to select Walmart as the seller... Read more
Apple resellers offer new 2020 Mac minis for...
Apple resellers are offering new 2020 Mac minis for up to $50 off Apple’s MSRP with prices available starting at $759. Shipping is free: (1) B&H Photo: – 2020 4-Core Mac mini: $759 $40 off MSRP... Read more
Sprint is offering the Apple iPhone 11 free t...
Did you miss out on Sprint’s recent free iPhone SE promotion? No worries. Sprint has the 64GB iPhone 11 available for $0 per month for new lines when you trade-in a qualifying phone in any condition... Read more
Apple has clearance 2019 13″ 1.4GHz MacBook P...
Apple has Certified Refurbished 2019 13″ 1.4GHz 4-Core Touch Bar MacBook Pros available today starting at $979 and up to $440 off original MSRP. Apple’s one-year warranty is included, shipping is... Read more
Apple restocks 2019 MacBook Airs starting at...
Apple has clearance, Certified Refurbished, 2019 13″ MacBook Airs available again starting at $779. Each MacBook features a new outer case, comes with a standard Apple one-year warranty, and is... Read more

Jobs Board

*Apple* Mac Desktop Support - Global Dimensi...
…Operate and support an Active Directory (AD) server-client environment for all Apple devices operating on the BUMED network + Leverage necessary industry enterprise Read more
Surgical Technologist III, *Apple* Hill Sur...
Surgical Technologist III, Apple Hill Surgical Center - Full Time Tracking Code D5.29.2020 Job Description Surgical Technologist III Apple Hill Surgical Center Read more
Security Officer - *Apple* Store - NANA (Un...
**Security Officer \- Apple Store** **Description** About NMS Built on a culture of safety and integrity, NMSdelivers award\-winning, integrated support services to Read more
Transition Into Practice Program (TIP) - Sept...
…Academy-Transition into Practice (TIP) Residency program at St Mary Medical Center in Apple Valley, CA. **We are seekingRegistered Nurses who are:** + New graduate Read more
Essbase Developer - *Apple* - Theorem, LLC...
Job Summary Apple is seeking an experienced, detail-minded Essbase developer to join our worldwide business development and strategy team. If you are someone who Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.