- •Contents
- •1.1 Fundamental Types
- •1.2 T Classes
- •1.3 C Classes
- •1.4 R Classes
- •1.5 M Classes
- •1.6 Static Classes
- •1.7 Buyer Beware
- •1.8 Summary
- •2 Leaves: Symbian OS Exceptions
- •2.1 Leaving Functions
- •2.3 Constructors and Destructors
- •2.4 Working with Leaving Functions
- •2.5 Trapping a Leave Using TRAP and TRAPD
- •2.6 LeaveScan
- •2.7 Summary
- •3 The Cleanup Stack
- •3.1 Using the Cleanup Stack
- •3.2 How Does the Cleanup Stack Work?
- •3.4 Using TCleanupItem for Customized Cleanup
- •3.5 Portability
- •3.6 An Incidental Note on the Use of Casts
- •3.7 Summary
- •5 Descriptors: Symbian OS Strings
- •5.3 Pointer Descriptors
- •5.4 Stack-Based Buffer Descriptors
- •5.5 Heap-Based Buffer Descriptors
- •5.6 Literal Descriptors
- •5.7 Summary
- •6 Good Descriptor Style
- •6.1 Descriptors as Parameters and Return Types
- •6.2 Common Descriptor Methods
- •6.3 The Use of HBufC Heap Descriptors
- •6.4 Externalizing and Internalizing Descriptors
- •6.5 The Overuse of TFileName
- •6.6 Useful Classes for Descriptor Manipulation
- •6.7 Summary
- •7 Dynamic Arrays and Buffers
- •7.1 CArrayX Classes
- •7.3 Why Use RArray Instead of CArrayX?
- •7.4 Dynamic Descriptor Arrays
- •7.5 Fixed-Length Arrays
- •7.6 Dynamic Buffers
- •7.7 Summary
- •8.1 Multitasking Basics
- •8.2 Event-Driven Multitasking
- •8.3 Working with Active Objects
- •8.4 Example Code
- •8.5 Threads Without an Active Scheduler
- •8.6 Application Code and Active Objects
- •8.7 Summary
- •9 Active Objects under the Hood
- •9.1 Active Object Basics
- •9.2 Responsibilities of an Active Object
- •9.3 Responsibilities of an Asynchronous Service Provider
- •9.4 Responsibilities of the Active Scheduler
- •9.5 Starting the Active Scheduler
- •9.6 Nesting the Active Scheduler
- •9.7 Extending the Active Scheduler
- •9.8 Cancellation
- •9.9 Request Completion
- •9.10 State Machines
- •9.11 Long-Running Tasks
- •9.14 Common Mistakes
- •9.15 Summary
- •10 Symbian OS Threads and Processes
- •10.2 Thread Priorities
- •10.3 Stopping a Running Thread
- •10.5 Exception Handling
- •10.6 Processes
- •10.7 Summary
- •11.2 How Do the Client and Server Fit Together?
- •11.3 How Do the Client and Server Communicate?
- •11.5 How Do Synchronous and Asynchronous Requests Differ?
- •11.6 How Is a Server Started?
- •11.7 How Many Connections Can a Client Have?
- •11.8 What Happens When a Client Disconnects?
- •11.9 What Happens If a Client Dies?
- •11.10 What Happens If a Server Dies?
- •11.15 How Many Outstanding Requests Can a Client Make to a Server?
- •11.16 Can Server Functionality Be Extended?
- •11.17 Example Code
- •11.18 Summary
- •12.2 Client Boilerplate Code
- •12.3 Starting the Server and Connecting to It from the Client
- •12.4 Server Startup Code
- •12.5 Server Classes
- •12.6 Server Shutdown
- •12.7 Accessing the Server
- •12.8 Summary
- •13 Binary Types
- •13.1 Symbian OS EXEs
- •13.2 Symbian OS DLLs
- •13.3 Writable Static Data
- •13.4 Thread-Local Storage
- •13.5 The DLL Loader
- •13.6 UIDs
- •13.8 Summary
- •14 ECOM
- •14.1 ECOM Architecture
- •14.2 Features of an ECOM Interface
- •14.3 Factory Methods
- •14.4 Implementing an ECOM Interface
- •14.5 Resource Files
- •14.6 Example Client Code
- •14.7 Summary
- •15 Panics
- •15.2 Good Panic Style
- •15.3 Symbian OS Panic Categories
- •15.4 Panicking Another Thread
- •15.5 Faults, Leaves and Panics
- •15.6 Summary
- •16 Bug Detection Using Assertions
- •16.3 Summary
- •17 Debug Macros and Test Classes
- •17.1 Heap-Checking Macros
- •17.2 Object Invariance Macros
- •17.3 Console Tests Using RTest
- •17.4 Summary
- •18 Compatibility
- •18.1 Forward and Backward Compatibility
- •18.2 Source Compatibility
- •18.3 Binary Compatibility
- •18.4 Preventing Compatibility Breaks
- •18.5 What Can I Change Without Breaking Binary Compatibility?
- •18.6 Best Practice: Planning for Future Changes
- •18.7 Compatibility and the Symbian OS Class Types
- •18.8 Summary
- •19 Thin Templates
- •20.1 Class Layout
- •20.3 Parameters and Return Values
- •20.4 Member Data and Functional Abstraction
- •20.5 Choosing Class, Method and Parameter Names
- •20.7 Summary
- •21 Good Code Style
- •21.1 Reduce the Size of Program Code
- •21.2 Use Heap Memory Carefully
- •21.3 Use Stack Memory Carefully
- •21.5 Optimize Late
- •21.6 Summary
- •Glossary
- •Bibliography and Online Resources
- •Index
3
The Cleanup Stack
Life is pleasant. Death is peaceful. It’s the transition that’s troublesome
Jimi Hendrix
This chapter discusses a fundamental part of Symbian OS, the cleanup stack. Symbian OS is designed to perform well with limited memory, and part of that design must inevitably consider memory management when errors occur. The cleanup stack manages memory which would otherwise be ”orphaned” (leaked) in the event of a leave.
But what, exactly, is ”orphaning”? In the previous chapter, I described why Symbian OS doesn’t use standard C++ exceptions, but instead handles exceptional conditions using ”leaves”. Code that can leave is, at some level, surrounded by a TRAP, or TRAPD, harness. If a leave occurs, control is transferred directly to the statement following the harness. In effect, the TRAP is equivalent to a setjmp and the leave to a longjmp. The stack memory is freed as the stack unwinds, but otherwise the stack frame is abandoned and no object destructors are called (which is unlike a standard C++ exception). This means that the destructors of any local variables or arguments passed by value, or objects created as member variables of either of these, will not be called. Some objects are ”leave-safe” – they do not need destructors and contain only data which can be destroyed as stack memory is freed by the leave. This data may consist of the basic, built-in types or other objects which contain such types. In Symbian OS these are called T classes (T for ”type”, described in detail in Chapter 1), the characteristic of which is that they may safely be created on the stack in functions where code may leave.
If a local variable is a pointer to an object on the heap, when a leave occurs the pointer is destroyed without freeing that heap memory, which becomes unrecoverable, causing a memory leak. The memory is said to be orphaned. This means that C class objects, which are always created on the heap, as described in Chapter 1, are not leave-safe. Unless they are otherwise accessible for safe destruction (for example, as member variables of an object which is destroyed regardless of the leave), the
30 |
THE CLEANUP STACK |
memory they occupy on the heap and any resources they own are orphaned by a leave. R class objects are generally not leave-safe either, since the resources they own must be freed in the event of a leave (through a call to the appropriate Close() or Release() type function). If this call cannot made by an object accessible after the leave, the resource is orphaned.
Consider the following code, which creates an object of a C class (CClanger) on the heap, referenced only by an automatic variable, clanger. After creating the object, a function which may potentially leave, InitializeL(), is called. The heap-based object is not leavesafe and neither the heap memory it occupies, nor any objects it owns, are destroyed if InitializeL() leaves.1
void UnsafeFunctionL()
{
CClanger* clanger = new (ELeave) CClanger();
clanger->InitializeL(); // Potential leaving function orphans clanger
... // Omitted for clarity delete clanger;
}
You could remove the potential memory leak by placing a TRAP, or TRAPD, macro around the call to InitializeL() to catch any leaves. However, as Chapter 2 explained, the use of TRAPs should be limited, where possible, to optimize the size and run-time speed of the compiled binary. I have only shown one call which could cause the object to be leaked; in a typical function there may be other operations that may leave. It would be inefficient to surround each with a TRAP. From an error-handling perspective, too, it’s often preferable to leave rather than return an error, as I discussed in Chapter 2.
When you combine both arguments, and consider the following irredeemably inefficient code, it’s clear that there should be an alternative to the TRAP idiom to protect objects which are not inherently leave-safe:
void SafeButInefficientL()
{
CClanger* clanger = new (ELeave) CClanger();
TRAPD(r,clanger->InitializeL()); // leave-safe, at a cost...
if (KErrNone!=r)
1 Before going any further, I should point out that the example is atypical of how a CBase- derived class should be instantiated. The Symbian OS coding standard recommends that all C classes wrap their construction and initialization code in a single static function, generally called NewL() or NewLC(), as illustrated later. To prevent a client from instantiating and initializing the object separately, as I’ve shown above, the standard advises that the constructors and initialization functions should be specified as protected in the class. This is known as two-phase construction, and is described further in Chapter 4.
USING THE CLEANUP STACK |
31 |
{
delete clanger; // delete clanger to prevent a leak User::Leave(r); // leave with the same error... horrible!
}
// Now do something else that may leave TRAP(r, clanger->DoSomethingElseL()); if (KErrNone!=r)
{
delete clanger; // Again, delete clanger to prevent a leak User::Leave(r);
}
... // Omitted for clarity delete clanger;
}
When a leave occurs, local variables are destroyed without first freeing any resources they own. The resource becomes unrecoverable, causing a memory leak, and is said to be ”orphaned”.
3.1 Using the Cleanup Stack
This is the cue for the cleanup stack, which is accessed through the static member functions of class CleanupStack, defined in e32base.h:
class CleanupStack
{
public:
IMPORT_C static void PushL(TAny* aPtr);
IMPORT_C static void PushL(CBase* aPtr);
IMPORT_C static void PushL(TCleanupItem anItem);
IMPORT_C static void Pop();
IMPORT_C static void Pop(TInt aCount);
IMPORT_C static void PopAndDestroy();
IMPORT_C static void PopAndDestroy(TInt aCount); IMPORT_C static void Check(TAny* aExpectedItem); inline static void Pop(TAny* aExpectedItem);
inline static void Pop(TInt aCount, TAny* aLastExpectedItem); inline static void PopAndDestroy(TAny* aExpectedItem);
inline static void PopAndDestroy(TInt aCount, TAny* aLastExpectedItem);
};
Objects that are not otherwise leave-safe should be placed on the cleanup stack before calling code that may leave. This ensures they are destroyed correctly if a leave occurs; in the event of a leave, the cleanup
32 |
THE CLEANUP STACK |
stack manages the deallocation of all objects which have been placed upon it.
The following code illustrates a leave-safe but simpler version than the
SafeButInefficientL() example:
void SafeFunctionL()
{
CClanger* clanger = new (ELeave) CClanger;
//Push onto the cleanup stack before calling a leaving function CleanupStack::PushL(clanger);
clanger->InitializeL(); clanger->DoSomethingElseL()
//Pop from cleanup stack
CleanupStack::Pop(clanger);
delete clanger;
}
If InitializeL() succeeds, the lines of code following the call pop the automatic CClanger pointer from the cleanup stack and delete the object to which it points. This code could equally well be replaced by a single call to CleanupStack::PopAndDestroy(clanger) which performs both the pop and a call to the destructor in one step. The function is useful if the object is no longer required and may also be destroyed. And if InitializeL() or DoSomethingElseL() leave, the clanger object is destroyed by the cleanup stack itself as part of leave processing, which I’ll discuss shortly.
Let’s move on to consider some general good practices when working with the cleanup stack. Objects are pushed on and popped off the cleanup stack in strict order: a series of Pop() calls must occur in the reverse order of the PushL() calls. It is possible to Pop() or PopAndDestroy() one or more objects without naming them, but it’s a good idea to name the object popping off. This averts any potential cleanup stack ”imbalance” bugs which can occur when Pop() removes an object from the cleanup stack which was not the one intended.
void ContrivedExampleL()
{// Note that each object is pushed onto the cleanup stack
//immediately it is allocated, in case the succeeding allocation
//leaves.
CSiamese* sealPoint = NewL(ESeal);
CleanupStack::PushL(sealPoint);
CSiamese* chocolatePoint = NewL(EChocolate);
CleanupStack::PushL(chocolatePoint);
CSiamese* violetPoint = NewL(EViolet);
CleanupStack::PushL(violetPoint);
CSiamese* bluePoint = NewL(EBlue);
CleanupStack::PushL(bluePoint);
USING THE CLEANUP STACK |
33 |
sealPoint->CatchMouseL();
//Other leaving function calls, some of which use the cleanup stack
...
//Various ways to remove the objects from the stack and delete them:
//(1) All with one anonymous call - OK, but potentially risky
//CleanupStack::PopAndDestroy(4);
//(2) All, naming the last object - Better
//CleanupStack::PopAndDestroy(4, sealPoint);
//(3) Each object individually to verify the code logic
//Note the reverse order of Pop() to PushL()
//This is quite long-winded and probably unnecessary in this
//example
CleanupStack::PopAndDestroy(bluePoint);
CleanupStack::PopAndDestroy(violetPoint);
CleanupStack::PopAndDestroy(chocolatePoint);
CleanupStack::PopAndDestroy(sealPoint);
}
At times, it’s quite difficult to keep track of what’s on the cleanup stack. Imbalance bugs can be difficult to find because they cause unexpected panics in code quite unrelated to where the error has occurred. To avoid them, it’s best to name what you Pop() off the cleanup stack explicitly, if only in the early stages of coding until you’re confident that the code is behaving as you expect. The checking is only performed in debug builds, so it has no impact on the speed of released code (although, if you do want to perform checking in a release build, you can use the CleanupStack::Check() function). If nothing else, it may help you track down bugs such as the one below where, under some conditions, an object is accidentally left on the cleanup stack when the function returns:
void AnotherContrivedExampleL(const TDesC& aString)
{
CClanger* clanger = AllocateClangerL(aString);
CleanupStack::PushL(clanger);
TBool result = NonLeavingFunction(clanger);
if (!result) |
|
return; |
// Whoops, clanger is still on the cleanup stack! |
DoSomethingThatLeavesL();
CleanupStack::PopAndDestroy(clanger);
}
But when should you Pop() an object from the cleanup stack? Well, it should never be possible for an object to be cleaned up more than once. If a pointer to an object on the cleanup stack is later stored elsewhere, say as a member variable of another object which is accessible after a leave, it should then be popped from the cleanup stack. If the pointer were retained on the cleanup stack, it would be destroyed by it, but the object storing the pointer to it would also attempt to destroy it, usually in
34 |
THE CLEANUP STACK |
its own destructor. Objects should be referred to either by another object or by the cleanup stack, but not by both.
void TransferOwnershipExampleL
{
CClanger* clanger = new (ELeave) CClanger(); CleanupStack::PushL(clanger); // Pushed - the next function may leave iMemberObject->TakeOwnershipL(clanger);// iMemberObject owns it so CleanupStack::Pop(clanger); // remove from cleanup stack
}
Likewise, you should never push class member variables (objects prefixed by ”i”, if the Symbian OS naming convention is followed) onto the cleanup stack. The object may be accessed through the owning object which destroys it when appropriate, typically in its destructor. Of course, this requires the owning object to be leave-safe.
Incidentally, no panic occurs if you push or pop objects onto the cleanup stack more than once. The problem occurs if the cleanup stack tries to destroy the same object more than once, either through multiple calls to PopAndDestroy() or in the event of a leave. Multiple deletion of the same C class object will attempt to free memory which has already been released back to the heap, which causes a panic.
If an object is pushed onto the cleanup stack and remains on it when the function returns, the function name must have the suffix ”C”. This indicates to the caller that, when the function returns successfully, the cleanup stack has additional objects upon it. It is typically used by CBase- derived classes which define static functions to instantiate an instance of the class and leave it on the cleanup stack. The C suffix indicates to the caller of a function that it is not necessary to push any objects allocated by the function onto the cleanup stack. The following code creates an object of type CSiamese (which I used in an earlier example) and leaves it on the cleanup stack. This function is useful because the caller can instantiate CSiamese and immediately call a leaving function, without needing to push the allocated object back onto the cleanup stack:
CSiamese* CSiamese::NewLC(TPointColor aPointColour)
{
CSiamese* me = new (ELeave) CSiamese(aPointColour);
CleanupStack::PushL(me); // Make this leave-safe...
me->ConstructL();
return (me) // Leave "me" on the cleanup stack for caller to Pop()
}
However, a word of warning with regard to functions that leave objects on the cleanup stack – you must take care when calling them from inside a TRAP harness. If objects are pushed onto the cleanup stack inside a TRAP and a leave does not occur, they must be popped off again before