You can use the Palm OS® Error Manager to display debugging information and unexpected runtime errors such as those that typically show up during program development. Final versions of applications or system software won't display the debugging information.
The Error Manager API consists of a set of functions for displaying an alert with an error message, file name, and the line number where the error occurred. If a debugger is connected, it is entered when the error occurs.
Palm OS also provides a try-and-catch exception-handling mechanism that applications can use for handling such runtime errors as out of memory conditions, user input errors, and the like.
This section discusses the following topics:
This chapter only describes programmatic debugging strategies; to learn how to use the available tools to debug your application, see the book Palm OS Programming Development Tools Guide.
Displaying Development Errors
The Error Manager provides a single function, ErrFatalErrorInContext()
, that displays an alert containing a supplied file name, a line number, and a string. You can insert calls to this function into your code in order to alert you to erroneous situations.
Often, you'll want to display certain error messages when you are debugging your code that should never appear in the production version of your program. The Error Manager defines a set of macros that each call ErrFatalErrorInContext()
and that are conditional, depending on the build type.
Setting the Build Type
The Error Manager uses the compiler define BUILD_TYPE
to control the set of error messages displayed. You can set the value of the compiler define to control which level of error checking and display is compiled into the application. Two BUILD_TYPE
levels are defined in Palm OS Cobalt:
Compiles in only |
|
Compiles in |
During development, it makes sense to set BUILD_TYPE
to BUILD_TYPE_DEBUG
. At this level, all Error Manager messages are displayed. Then, when you are ready to release your application, set BUILD_TYPE
to BUILD_TYPE_RELEASE
.
NOTE: Because the
ErrFatalError()
and ErrFatalErrorIf()
macros display a message regardless of the build type, they should only be used for those unrecoverable situations that can be communicated to the end user. For messages that aid in the debugging process but aren't to be presented to the end user, use DbgOnlyFatalError()
and DbgOnlyFatalErrorIf()
instead.
Displaying Error Messages
Rather than calling ErrFatalErrorInContext()
, most applications make use of the Error Manager's compiler macros instead. These macros employ ErrFatalErrorInContext()
to display a fatal alert dialog on the screen. There are four macros: ErrFatalError()
, ErrFatalErrorIf()
, DbgOnlyFatalError()
, and DbgOnlyFatalErrorIf()
.
-
ErrFatalError()
andDbgOnlyFatalError()
always display the error message on the screen ifBUILD_TYPE
isBUILD_TYPE_DEBUG
.DbgOnlyFatalError()
does nothing ifBUILD_TYPE
isBUILD_TYPE_RELEASE
. -
ErrFatalErrorIf()
andDbgOnlyFatalErrorIf()
display the error message only if their first argument istrue
and ifBUILD_TYPE
isBUILD_TYPE_DEBUG
.DbgOnlyFatalErrorIf()
does nothing ifBUILD_TYPE
isBUILD_TYPE_RELEASE
.
The first argument to ErrFatalErrorIf()
and DbgOnlyFatalErrorIf()
is a Boolean value that controls whether or not the message (the second parameter) is displayed. Typically, the Boolean parameter is an in-line expression that evaluates to true
if there is an error condition. As a result, both the expression that evaluates the error condition and the message text are left out of the compiled code when error checking is turned off. You can use either ErrFatalErrorIf()
or ErrFatalError()
, but using ErrFatalErrorIf()
makes your source code cleaner. For example, assume your source code looks like this:
result = DoSomething(); ErrFatalErrorIf (result < 0, "unexpected result from DoSomething");
With error checking turned on, this code displays an error alert dialog if the result from DoSomething()
is less than 0. With error checking turned off, both the expression evaluation result < 0
and the error message text are left out of the compiled code.
The same net result can be achieved by the following code:
result = DoSomething(); #if BUILD_TYPE != BUILD_TYPE_RELEASE if (result < 0) ErrFatalError ("unexpected result from DoSomething"); #endif
However, this solution is longer and requires more work than simply calling ErrFatalErrorIf()
. It also makes the source code harder to follow.
The Try-and-Catch Mechanism
The operating system is aware of the machine state of the Palm Powered™ device and can therefore correctly save and restore this state. The built-in try-and-catch of the compiler can't be used because it's machine dependent.
Try-and-catch is basically a neater way of implementing a goto
if an error occurs. A typical way of handling errors in the middle of a function is to go to the end of the function as soon as an error occurs and have some general-purpose cleanup code at the end of every function. Errors in nested functions are even trickier because the result code from every subroutine call must be checked before continuing.
When you use the ErrTry()
andErrCatch()
macros, you are providing the compiler with a place to jump to when an error occurs. You can go to that error handling routine at any time by calling ErrThrow()
. When the compiler sees the ErrThrow() call, it performs a goto1
to your error handling code. The greatest advantage to calling ErrThrow()
, however, is for handling errors in nested subroutine calls.
Even if ErrThrow()
is called from a nested subroutine, execution immediately goes to the same error handling code in the higher-level call. The compiler and runtime environment automatically strip off the stack frames that were pushed onto the stack during the nesting process and go to the error-handling section of the higher-level call. You no longer have to check for result codes after calling every subroutine; this can greatly simplify your source code and reduce its size.
Using the Try-and-Catch Mechanism
Listing 13.1 illustrates the possible layout for a typical routine using the try-and-catch mechanism.
Listing 13.1 Try-and-Catch Mechanism Example
ErrTry { p = MemPtrNew(1000); if (!p) ErrThrow(errNoMemory); MemSet(p, 1000, 0); CreateTable(p); PrintTable(p); } ErrCatch(err) { // Recover or clean up after a failure in the // above Try block."err" is an int // identifying the reason for the failure. // You may call ErrThrow() if you want to // jump out to the next Catch block. // The code in this Catch block doesn't // execute if the above Try block completes // without a Throw. if (err == errNoMemory) ErrFatalError("Out of Memory"); else ErrFatalError("Some other error"); } ErrEndCatch // You must structure your code exactly as // above. You can't have an ErrTry without an //ErrCatch { } ErrEndCatch, or vice versa.
Any call to ErrThrow()
within the ErrTry()
block results in control passing immediately to the ErrCatch()
block. Even if the subroutine CreateTable()
called ErrThrow()
, control would pass directly to the ErrCatch()
block. If the ErrTry()
block completes without calling ErrThrow()
, the ErrCatch()
block is not executed.
You can nest multiple ErrTry()
blocks. For example, if you wanted to perform some cleanup at the end of CreateTable()
in case of error,
- Put
ErrTry()
andErrCatch()
blocks inCreateTable()
. - Clean up in the
ErrCatch()
block first. - Call
ErrThrow()
to jump to the top-levelErrCatch()
.
Summary of Debugging API
1. longjmp
, actually.