Exception Handling (TRAP), Object Creation and CleanupStack
Symbian OS has been designed for devices with limited memory and battery driven. When we allocate memory for our objects we must make sure that the allocated memory are released. While we allocate memory then some object could be partially constructed. If the object construction is not complete then how the partially constructed object can be cleaned up? All these are solved by Symbian CleanupStack.
Throwing Exception in SymbianBefore we start our about CleanupStack let's take a look about throwing exception. In Symbian OS we throw exception by any of the following statement. For leave related code is implemented as static function in euser.lib (we include in our mmp file LIBRARY euser.lib for using these.)
In Symbian OS when we want to throw an exception we can use any of these depending on case. In Symbian we express an error by negative number. For example not found is defined as const TInt KErrNotFound=(-1); When something is not found then we throw an exception with KErrNotFound. Let's see some example use case of these statements.
- User::Leave(TInt aReason);
{
...
/* trying to open a file that is not found, so we throw KErrNotFound
*/
User::Leave(KErrNotFound);
/* throw an exception, that will be caught by TRAP or TRAPD macro.
*/
...
} - User::LeaveNoMemory();
{
...
/* trying to allocate memory from heap but there is no available memory, we use following statement
*/
User::LeaveNoMemory();
/* This is equivalent User::Leave(KErrNoMemory);Throwing an exception -4.
*/
...
}
- User::LeaveIfError(TInt aReason);
{
...
/*We are trying to call a method that returns 0 if no error and returns an error code (-ve value)
*/
User::LeaveIfError(CallingAnotherThatreturnTInt());
/* If CallingAnotherThatreturnTInt() returns an 0 nothing will happen but if returns -ve value then an exception will be thrown.
*/
...
}
- User::LeaveIfNull(TAny *aPtr);
{
...
/*We receive an pointer that can be NULL, If NULL we want to throw an exception
*/
User::LeaveIfNull(aPointerParameter);
/* If aPointerParameter is NULL then we throw an exception. This is equivalent User::Leave(KErrNoMemory);Throwing an exception -4.
*/
...
}
Catching Exception in Symbian
From C++ we know that when we try to execute some statement and if the statement fails then we throw an exception. We use try-catch block for that purposes. In side try we throw the exception. In Symbian, we use same mechanism but that is hidden by TRAP/TRAPD macro.
TInt error = KErrNone;
TRAP(error, MyLeavingFnL());
In the above statement, first we declare a variable error of type TInt. This variable will be used to hold the thrown value by MyLeavingFnL() if it throws an exception. If it does not throw any exception then the value of error will not changed.
Let's we have following implementation for our MyLeavingFnL();
Tint MyClass::MyLeavingFnL()
{
TInt ret = KErrNone;
//We try to connect to service that we may have or may not have permission.
//If we have permission then the status will be 0 otherwise it will be KErrAccessDenied.
TInt status = ConnectToOtherService();
if(status != KErrNone) // KErrNone is 0
{
// This status will be caught by TRAP macro in above statement
//error in the above statement will be same as status
User::Leave(status); //status is other than KErrNone, so throw an exception.
}
return ret;// We are not catching the return value
}
Following slide shows it visually.

Now let's use the return value of MyLeavingFnL(); TInt error = KErrNone;
TInt ret = KErrNone;
TRAP(error, ret = MyLeavingFnL());
If the function returns KErrNone and no exception occurs then error and ret are same. error is caught value and ret is return value of the function. In our example if the function really leaves what would be the value of ret? In this case ret has no meaning. TRAP and TRAPD are almost same. Only difference is that with D means the variable is declared. If we use TRAP then we don't need to declare the variable that holds the caught value, in our case, we don't need to declare error variable. The variable is declared inside the TRAPD macro.
TInt ret = KErrNone;
TRAPD(error, ret = MyLeavingFnL());
Here is the macro definition for TRAPD defined in e32cmn.h .
TRAPD (_r, _s) TInt _r; \
{ \
_r = 0; \
{ TRAP_INSTRUMENTATION_START; } \
try { \
__WIN32SEHTRAP \
TTrapHandler* ____t = User::MarkCleanupStack(); \
_s; \
User::UnMarkCleanupStack(____t); \
{ TRAP_INSTRUMENTATION_NOLEAVE; } \
__WIN32SEHUNTRAP \
} \
catch (XLeaveException& l) \
{ \
_r = l.GetReason(); \
{ TRAP_INSTRUMENTATION_LEAVE(_r); } \
} \
catch (...) \
{ \
User::Invariant(); \
} \
}
Object Creation in Symbian OS
Default C++ constructor has a problem that it can't return any value.
Symbian OS uses new (overloaded version) to create an object. It uses overloaded version of new for throwing exception if memory allocation fails it can throw an exception with a call User::Leave(KErrNoMemory).
Let's see this example:
Tint MyClass::MyAnotherLeavingFnL()
{
TInt ret = KErrNone;
CClass* c = new(ELeave) CClass; // Try to allocate memory.
// if no memory it will throw an exception KErrNoMemory,
//caller does not need to check if c is NULL.
c->DoSomethingL(); // potential leaving function orphans c
//if it leaves
delete c; //If DoSomethingL() leaves then this statement will
//not be called.
return ret;
}
Why CleanupStack is used Symbian
CleanupStack is used to clean all constructed objects that has been pushed on CleanupStack when a leave occurs. CleanupStack does not clean all objects from the CleanupStack but upto a certain level (The level is inserted to the CleanupStack when when use TRAP/TRAPD).
Let's take an example,
void CX::UseL()
{
CY* pcy=new(ELeave) CY;
pcy->UseYL();
delete pcy;
}
CX* x=new(ELeave) CX;
// Try to allocate memory, that may leave
x->UseL();// Use the object
delete x;// free memory
What is going to happen if there is no enough memory for CY in CX::UseL()? If there is no memory when we allocate for pcy then we can never delete our x object. This will cause memory leak. To solve this problem, we use CleanupStack. We push the pointer to the CleanupStack and when there is a leave then system destroys all pointers that was stored in the CleanupStack. There is one CleanupStack per thread. If we have GUI application then we don't need to install CleanupStack by ourselves. GUI framework does it for us. Above code can be re written by using CleanupStack.
void CX::UseL()
{
CY* pcy=new(ELeave) CY;
//Push the object to CleanupStack
CleanupStack::PushL(pcy);
//Use the object, that may leave
pcy->UseYL();
//Pop object from stack and destroy it
CleanupStack::PopAndDestroy();
//Following 2 lines are equivalent to PopAndDestroy()
//CleanupStack::Pop();
//delete pcy;
}
CX* x=new(ELeave) CX;
// Try to allocate memory, that may leave
CleanupStack::PushL(pcy);
x->UseL();// Use the object
//Remove from stack and destroy
CleanupStack::PopAndDestroy();
To see how CleanupStack works in practice, you could download this power point slide (2 pages)
How to install CleanupStack for a thread
In console application we see this type of statements. We use CTrapCleanup::New() that install the CleanupStack for current thread, so we can use CleanupStack related functions. We can easily test this by removing CTrapCleanup::New() and if we use CleanupStack related functions such as Pop, Push etc then we get a panic.
CTrapCleanup* cleanup=CTrapCleanup::New(); // get clean-up stack
TRAPD(error,callExampleL()); // Use functions that uses CleanupStack
__ASSERT_ALWAYS(!error,User::Panic(KTxtEPOC32EX,error));
delete cleanup; // destroy clean-up stack
Two-phase construction
CExample* foo = new CExample();
Call new operator, which allocates CExample object on the heap (if enough memory available) and then calls the constructor of class CExample to initialize the object
If CExample constructor itself leaves => memory orphaned
=> no code within a C++ constructor should ever leave
=> two-phase construction:
A basic constructor which cannot leave, e.g CExample()
new operator calls this base-class constructor
A class method, e.g. ConstructL called separately once object (allocated and constructed) has been pushed onto cleanup stack
Construct compound classes in two phases
the normal constructor CClass::CClass() for all safe construction
the second phase constructor ConstructL to safely construct things that can leave
Factory function NewL ties two phases together
Self contained classes only require single phase construction
class CClass : public CBase
{
public:
static CClass* NewL(TInt aInt,CBase& aObj);
~CClass();
private: // or protected
CClass(TInt aInt);
void ConstructL(CBase& aObj);
private:
TInt iInt;
CSimple* iSimple;
CCompound* iCompound;
};
CClass* CClass::NewL(TInt aInt,CBase& aObj)
{
CClass* self=new(ELeave) CClass(aInt);//1st phase construction
CleanupStack::PushL(self);
self->ConstructL(aObj);//2nd phase construction
//Do additional initialization in ConstructL() that may fail
CleanupStack::Pop(self);
return self;
}
1st phase construction
CClass* CClass::NewL(TInt aInt,CBase& aObj)
{
CClass* self=new(ELeave) CClass(aInt);
...
CClass::CClass(TInt aInt)
:iInt(aInt) // do safe construction
{
}
void CClass::ConstructL(CBase& aObj)
{
iSimple=new(ELeave) CSimple;
iCompound=CCompound::NewL(aObj);
}
CClass::~CClass()
{
delete iSimple; // delete child objects
delete iCompound;
}
NewL() & NewLC()
It is common practice to implement NewL() by reusing NewLC(). When we need to use a object many times then it is good to keep the object on CleanupStack and later after using it we can pop and destroy it.
NewLC -> at the end there is C means we store the object on CleanupStack. CClass* CClass::NewLC(TInt aInt,CBase& aObj)
{
CClass* self=new(ELeave) CClass(aInt);
CleanupStack::PushL(self);
self->ConstructL(aObj);
return self;
}
CClass* CClass::NewL(TInt aInt,CBase& aObj)
{
CClass* self=NewLC(aInt,aObj);
CleanupStack::Pop(self);
return self;
}
Here is example how we can use NewL().
CClass * pcl = CClass::NewL(10, aobject); CleanupStack::PushL(self);
pcl->DoSomeThingL(); CleanupStack::Pop(self);
delete pcl;
Here is example how we can use NewLC().
CClass * pcl = CClass::NewLC(10, aobject);
pcl->DoSomeThingL();
pcl->CallSomeThingL();
CleanupStack::Pop(self);
delete pcl;
Inside CleanupStack
In this section we describes how cleanupstack is working internally.
CTrapCleanup object has an instance of TCleanupTrapHandler which has a pointer (that is allocated when we call CTrapCleanup* cleanup=CTrapCleanup::New(); ) declared as
CCleanup* iCleanup; . CCleanup is which holds objects when we push it to cleanupstack.
CCleanup has been declared in e32base.h as follows:
class CCleanup : public CBase
{
public:
IMPORT_C void PushL(TAny* aPtr);
IMPORT_C void PushL(CBase* anObject);
IMPORT_C void PushL(TCleanupItem anItem);
IMPORT_C void Pop();
..
protected:
...
};
From the previous class CCleanup declarion we see that PushL() has 3 variations. PushL(TAny* aPtr) takes TAny * type, PushL(CBase* anObject) takes CBase* type (class derived from CBase) and PushL(TCleanupItem anItem) takes TCleanupItem type (where we can define our own function that will be called when CleanupStack::PopAndDestroy() is called).
See Own CleanupItem(SymbianCleanupItemTest.zip) example from the download area. How these 3 types can be used.
Download area
CleanupStack and TRAPD in power point slide
