Error handling is a critical issue for Symbian OS programming. This is not because Symbian OS is more prone to errors than other embedded operating systems, but because Symbian takes robustness seriously.
There are a range of possible causes of error: running out of memory is the most obvious, but communications errors and other external events can occur. Programming properly for robustness is considerably harder than just programming for when everything goes according to plan, but Symbian OS provides some help in the form of Leaves and the cleanup stack.
Leaves and Traps
Leaving code is a form of exception handling. The User::Leave() method is equivalent to throwing an exception, and catching is achieved as part of a TRAP or TRAPD macro. The User::Leave() method takes one integer argument, which is the error code (this should not be KErrNone). If User::Leave() is called, control is immediately returned to the innermost TRAP. By convention, any method that can leave has a name that ends in L – this alerts developers who use the method to the possibility of a leave. Obviously, if a method does not itself call User::Leave() but calls other methods that can leave, then it is itself a leaving function. There are a few exceptions to the naming convention – either because a meaningful method name ends with L but does not leave, or because a developer has neglected to put L on the end of a leaving function – but it is generally observed and you should follow it as well.
An alternative to leaving is to return an error value. Many methods in Symbian OS return a TInt error code rather than leaving. In general, a leave should be considered exceptional; if a method can commonly return an error then it should probably return an error code rather than leaving. Leaving code should normally be used where an error will cause control to unwind up a number of levels rather than just one. One reason for not using error codes everywhere is that setting up a TRAP macro and calling Leave has an overhead: it is slower than just returning an error code.
TRAPD(retVal, DoServiceL(aCmd, msgId));
if(retVal != KErrNone)
The TRAPD statement declares the variable retVal and then calls DoServiceL(). In this case DoServiceL() must be a method of type void. If DoServiceL() does not leave (which will be the normal case) then retVal will be set to KErrNone. If DoServiceL() does leave then retVal will be set to the leave code, which can then be handled. In this case, any error code will cause an error code to be returned, but other code might need to take different actions depending on the specific code.
The Cleanup Stack
The cleanup stack is a stack of pointers (and some other information). When an object is created on the heap, a pointer to it can be pushed on to the stack. Whenever a TRAP macro is used, a marker is placed on the cleanup stack, and if a leave takes place then any objects left on the stack since the TRAP macro will automatically be deleted.
Pointers to heap objects can be pushed on to the cleanup stack but pointers to objects on the stack cannot.This means that an object on the stack will not get deleted during a leave, so you should place only T-class objects on the stack, not C-class objects.The cleanup stack is a true stack – you push pointers on to the stack and pop them off. Therefore, it is not possible to pop an object off the stack unless it is the top object on the stack, since the sequence of pops must be the exact reverse of the sequence of pushes.
One of the common causes of leaving is running out of memory, Symbian OS utilizes a pattern for construction of complex objects referred to as two-phase construction. In the first phase, an object is allocated on the heap, but the constructor must not leave itself and must not call any leaving functions. Once the object has been constructed, it can be pushed on to the cleanup stack and a second-phase constructor, normally called ConstructL(), can be called to set up the rest of the data. Often this behavior is wrapped up in a static method that will allocate and construct an object.
All the preceding discussion is about handling foreseeable runtime errors. Unfortunately, the same techniques do not work for programming errors. The key intention in such cases is to detect the error during development and debugging and, if absolutely necessary, to terminate a program neatly if the error occurs in a release environment.
Symbian OS provides a User::Panic() method that can be called. It takes a panic string of up to 16 characters and a panic code. If you call User::Panic(), your thread (actually your process) gets terminated and a message is provided. In a debugging environment, Symbian OS will provide you with as much information as it can; in a release environment, the information will be limited to the text and code that you provided. In a release environment, the end-user will be the person who sees the panic and may (you hope) report the error with the provided information.
The standard way of using panics is by means of asserts. Symbian OS provides the __ASSERT_DEBUG and __ASSERT_ALWAYS macros. They follow the same pattern of including a condition and an expression. The condition is evaluated and, if it is false, the expression is evaluated, which normally means calling User::Panic().
There is a real difference of opinion about putting asserts into release code, but it is definitely good practice to use as many checks as possible in your debug code.