Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

HOW DOES THE CLEANUP STACK WORK?

35

exiting the macro, otherwise a panic occurs. This is because the cleanup stack stores nested levels of objects to destroy; each level is confined within a TRAP, and must be empty when the code inside it returns. Thus, the following code panics with E32USER-CBASE 71,2 when it returns to the TRAPD macro.

CExample* MakeExample()

{

CExample* pExample = TRAPD(r, CExample::NewLC()); // This will panic

return (pExample);

}

Objects that are not leave-safe should be placed on the cleanup stack before calling code that may leave. The cleanup stack manages the deallocation of all objects which have been placed upon it in the event of a leave.

3.2 How Does the Cleanup Stack Work?

I’ll use this section to go into how the cleanup stack works in some detail. If you came to this chapter only with an interest in how to use the cleanup stack, feel free to skip this section, since I have now covered the most important points for CBase-derived classes. Either read on anyway, or rejoin the chapter at Section 3.3, ”Using the Cleanup Stack with non-CBase Classes”, where I’ll go into how to use the cleanup stack to make leave-safe objects of R classes, heap-based T classes and objects referenced through M class pointers.

As I’ve already described, the cleanup stack stores pointers to objects to be destroyed in the event of leave. The pointers are stored in nested ”levels” associated with the TRAP macro under which they were pushed onto the stack. In the following code, if FeedL() leaves, only objects placed on the cleanup stack inside the TRAP macro are destroyed. Thus the CChilli object on the cleanup stack is destroyed, but the CExample object, stored on the cleanup stack prior to the call inside the TRAP macro, is not destroyed because the leave is only propagated to the level of the enclosing TRAP.

class CChilli;

class TCalibrateDragon

2 The panic is described as follows in the panic documentation of the SDK: ”This panic is raised when TRAPs have been nested and an attempt is made to exit from a TRAP nest level before all the cleanup items belonging to that level have been popped off the cleanup stack.”

36

THE CLEANUP STACK

{

public:

FeedL(TChilliPepper aChilli); private:

void TasteChilliL(CChilli* aChilli);

... // Omitted for clarity };

CExample* CExample::NewL(TChilliPepper aChilli)

{

CExample* me = new (ELeave) CExample();

CleanupStack::PushL(me); // Later functions may leave

TCalibrateDragon dragon;

TRAPD(r, dragon->FeedL(aChilli)); // TRAP this to act on the result

// Do something dependent on whether FeedL() left if (KErrNone==r)

{//... omitted for clarity

}

else

{//... omitted for clarity

}

CleanupStack::Pop(me); return (me)

}

void TCalibrateDragon::FeedL(TChilliPepper aChilli)

{

CChilli* chilli = CChilli::NewL(aChilli);

CleanupStack::PushL(chilli);

TasteChilliL(chilli); // Function leaves if chilli is too hot CleanupStack::PopAndDestroy(chilli);

}

As the example shows, in the event of a leave, the cleanup stack destroys the objects it stores for that particular TRAP level. But how does it know how to destroy them? What does the cleanup stack class look like? How is the cleanup stack created? And what, the ever-cautious reader may ask, happens if CleanupStack::PushL() leaves?

Well, let’s start at the beginning, the creation of a cleanup stack. You won’t have to create a cleanup stack if you are writing a GUI application or a server, since both have cleanup stacks created for them as part of their framework code. However, if you are writing a simple console test application or creating a separate thread, and you need to use the cleanup stack or call code that does, you will need to allocate a cleanup stack, since if there is no cleanup stack allocated for a thread, a call to

CleanupStack::PushL() causes an E32USER-CBASE 32 panic.

CTrapCleanup* theCleanupStack = CTrapCleanup::New();

... // Code that uses the cleanup stack, within a TRAP macro

delete theCleanupStack;

HOW DOES THE CLEANUP STACK WORK?

37

The name given to the CTrapCleanup object is up to you, since the object is only referenced directly when you come to destroy it again. As you’ve already seen, access to the cleanup stack is through the static member functions of the CleanupStack class, defined in e32base.h. But how does this work?

Well, it’s slightly confusing. When you create an object of type

CTrapCleanup, the following occurs in CTrapCleanup::New():

1.The current trap handler for the thread is stored.

2.Within CTrapCleanup, an iHandler object of type TCleanupTrapHandler is created (this owns a CCleanup object which actually contains the code which implements the cleanup stack).

3.A call to User::SetTrapHandler()then installs that TCleanupTrapHandler object as the new trap handler for the thread.

When you call CleanupStack::PushL() or CleanupStack:: Pop(), the static functions call User::TrapHandler() to acquire the installed TCleanupTrapHandler object and gain access to the CCleanup object. Likewise, when a leave occurs, the trap handler catches it and invokes the CCleanup object to cleanup any objects it has stored (see Figure 3.1).

The CleanupStack class has three overloads of the PushL() function, which I’ll discuss shortly. But first, let me allay any concerns you may have about the fact that PushL() can leave.

The reason that PushL() is a leaving function is because it may allocate memory to store the cleanup pointer and, thus, may fail in low memory situations. However, you don’t need to worry that the object you are pushing onto the cleanup stack will be orphaned if a leave does occur. The cleanup stack is created with at least one spare slot. When you call PushL(), the pointer you pass is added to the vacant slot, and then, if there are no more vacant slots available, the cleanup stack code attempts to allocate one for the next PushL() operation. If that allocation fails, a leave occurs, but your pointer has already been stored safely and the object it refers to is safely cleaned up.

In fact, the cleanup stack code is more efficient than my simplistic explanation above; it allocates more than a single slot at a time. By default, it allocates four slots. In addition, it doesn’t release slots when you Pop() objects out of them, so a PushL() frequently does not make any allocation and can be guaranteed to succeed. This can be useful in circumstances where you acquire more than one pointer that is not leave-safe to push onto the cleanup stack. You can use this knowledge to expand the cleanup stack to contain at least the number of slots you need, then create the objects and safely push them onto the vacant slots.

38

 

 

 

 

 

 

THE CLEANUP STACK

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

CleanupStack

 

 

 

 

 

 

 

CTrapCleanup

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

static void PushL(CBase*)

 

 

 

 

 

 

CTrapCleanup* New()

 

 

static void PushL(TAny*)

 

 

 

 

 

...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

static void PushL(TCleanupItem)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

static void Pop()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

static void PopAndDestroy()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

iHandler

 

 

 

 

 

 

iOldHandler

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

TCleanupTrapHandler

 

 

 

 

TTrapHandler

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Trap()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

UnTrap()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

iCleanup

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

CCleanup

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

void PushL(CBase*)

 

 

 

 

 

 

 

 

 

 

 

void PushL(TAny*)

 

 

 

 

 

 

 

 

 

 

void PushL(TCleanupItem)

 

 

 

 

 

 

 

 

 

 

void Pop()

 

 

 

 

 

 

 

 

 

 

void PopAndDestroy()

 

 

 

 

 

 

 

 

 

 

...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 3.1 Cleanup stack class hierarchy

 

 

 

 

The cleanup stack stores pointers to objects to be destroyed using nested ”levels” associated with the TRAP macro under which they were PushL()’d.

3.3 Using the Cleanup Stack with Non-CBase Classes

If you’ve rejoined after skipping the detailed section on how the cleanup stack works, welcome back. Up to this point, we’ve only really considered one of the overloads of the CleanupStack::PushL() function: PushL(CBase* aPtr), which takes a pointer to a CBase-derived object. When CleanupStack::PopAndDestroy() is called for that object, or if leave processing occurs, the object is destroyed by invoking delete on the pointer, which calls the virtual destructor of the CBase- derived object. As mandated by C++, the object is destroyed by calling destructor code in the most-derived class first, moving up the inheritance

USING THE CLEANUP STACK WITH NON-CBase CLASSES

39

hierarchy calling destructors in turn and eventually calling the empty CBase destructor. This is the reason that the CBase class has a virtual destructor – so that C class objects can be placed on the cleanup stack and destroyed safely if a leave occurs.

If you define a C class and forget to derive it from CBase, bad things happen if the cleanup stack tries to destroy it. If the object does not derive from CBase, the CleanupStack::PushL(TAny*) overload will be used to push it onto the cleanup stack, instead of the CleanupStack::PushL(CBase*) overload. Using this overload means that if the cleanup stack later comes to destroy the object, its heap memory is simply deallocated (by invoking User::Free()) and no destructors are called on it. In consequence, if your heap-based class does not inherit from CBase, directly or indirectly, and you push it onto the cleanup stack, its destructor is not called and it may not be cleaned up as you expect.

The CleanupStack::PushL(TAny*) overload is used when you push onto the cleanup stack a pointer to a heap-based object which does not have a destructor, for example, a T class object or a struct which has been allocated, for some reason, on the heap. Recall that T classes do not have destructors, and thus have no requirement for cleanup beyond deallocation of the heap memory they occupy.

Likewise, when you push heap descriptor objects (of class HBufC, described in Chapters 5 and 6) onto the cleanup stack, the CleanupStack::PushL(TAny*) overload is used. This is because HBufC objects are, in effect, heap-based objects of a T class. Objects of type HBufC require no destructor invocation, because they only contain plain built-in type data (a TUint representing the length of the descriptor and a character array of type TText8 or TText16). The only cleanup necessary is that required to free the heap cells for the descriptor object.

The third overload, CleanupStack::PushL(TCleanupItem), takes an object of type TCleanupItem, which allows you to put other types of object or those with customized cleanup routines onto the cleanup stack. A TCleanupItem object encapsulates a pointer to the object to be stored on the cleanup stack and a pointer to a function that provides cleanup for that object. The cleanup function can be a local function or a static method of a class.

For reference, here’s the definition of the TCleanupItem class and the required signature of a cleanup function, which takes a single TAny* parameter and returns void:

// From e32base.h

typedef void (*TCleanupOperation)(TAny*);

//

class TCleanupItem

{

public:

40

THE CLEANUP STACK

inline TCleanupItem(TCleanupOperation anOperation);

inline TCleanupItem(TCleanupOperation anOperation, TAny* aPtr);

private:

TCleanupOperation iOperation;

TAny* iPtr;

friend class TCleanupStackItem;

};

A call to PopAndDestroy() or leave processing removes the object from the cleanup stack and calls the cleanup function provided by the

TCleanupItem.

class RSample

{

public:

static void Cleanup(TAny *aPtr); void Release();

... // Other member functions omitted for clarity };

// cleanup function

void RSample::Cleanup(TAny *aPtr)

{// Casts the parameter to RSample and calls Release() on it // (error-checking omitted) (static_cast<RSample*>(aPtr))->Release();

}

void LeavingFunctionL()

{

RSample theExample;

TCleanupItem safeExample(RSample::Cleanup, &theExample); CleanupStack::PushL();

//Some leaving operations called theExample.InitializeL(); // Allocates the resource

...

//Remove from the cleanup stack and invoke RSample::Cleanup() CleanupStack::PopAndDestroy();

}

You’ll notice that the static Cleanup() function defined for RSample casts the object pointer it receives to a pointer of the class to be cleaned up. This is to allow access to the non-static Release() method which performs the cleanup. For code to be generic, the TCleanupItem object must store the cleanup pointer and pass it to the cleanup operation as a TAny* pointer.

The curious reader may be wondering how the cleanup stack discriminates between a TCleanupItem and objects pushed onto the cleanup stack using one of the other PushL() overloads. The answer is that it doesn’t – all objects stored on the cleanup stack are of type

TCleanupItem. The PushL() overloads that take CBase or TAny

USING THE CLEANUP STACK WITH NON-CBase CLASSES

41

pointers construct a TCleanupItem implicitly and store it on the cleanup stack. As I’ve already described, the cleanup function for the CBase* overload deletes the CBase-derived object through its virtual destructor. For the TAny* overload, the cleanup function calls User::Free(), which simply frees the allocated memory.

Symbian OS also provides three utility functions, each of which generates an object of type TCleanupItem and pushes it onto the cleanup stack, for cleanup methods Release(), Delete() and Close(). The functions are templated so the cleanup methods do not have to be static.

CleanupReleasePushL(T& aRef)

Leave processing or a call to PopAndDestroy() will call Release() on an object of type T. To illustrate, here’s the implementation from e32base.inl:

// Template class CleanupRelease template <class T>

inline void CleanupRelease<T>::PushL(T& aRef) {CleanupStack::PushL(TCleanupItem(&Release,&aRef));}

template <class T>

void CleanupRelease<T>::Release(TAny *aPtr) {(STATIC_CAST(T*,aPtr))->Release();}

template <class T>

inline void CleanupReleasePushL(T& aRef) {CleanupRelease<T>::PushL(aRef);}

You may be wondering about the use of STATIC_CAST rather than the C++ standard static_cast I used in RSample::Cleanup(). Fear not, I’ll explain why it’s used at the end of this chapter, in Section 3.6 (”An Incidental Note on the Use of Casts”).

And here’s an example of its use to make an object referred to by an M class3 pointer leave-safe. The M class does not have a destructor; instead the object must be cleaned up through a call to its Release() method, which allows an implementing class to customize cleanup:

class MExtraTerrestrial // See Chapter 1 for details of mixin classes

{

public:

virtual void CommunicateL() = 0;

... // The rest of the interface is omitted for clarity virtual void Release() = 0;

};

3 M classes are described in more detail in Chapter 1 and define a ”mixin” interface which is implemented by a concrete inheriting class, which will, typically, also derive from

CBase.

42

THE CLEANUP STACK

class CClanger : public CBase, MExtraTerrestrial

{

public:

static MExtraTerrestrial* NewL();

virtual void CommunicateL(); // From MExtraTerrestrial virtual void Release();

private:

CClanger();

CClanger();

private:

...

};

void TestMixinL()

{

MExtraTerrestrial* clanger = CClanger::NewL();

CleanupReleasePushL(*clanger); // The pointer is deferenced here

... // Perform actions which may leave CleanupStack::PopAndDestroy(clanger); // Note pointer to the object

}

Notice the asymmetry in the calls to push the object onto the cleanup stack and to pop it off again. As you can see from the definition above, CleanupReleasePushL() takes a reference to the object in question, so you must deference any pointer you pass it. However, if you choose to name the object when you Pop() or PopAndDestroy() it, you should pass a pointer to the object. This is because you’re not calling an overload of Pop().

The definition of CleanupClosePushL() is similarly asymmetric, but for CleanupDeletePushL() you should pass in a pointer. This is because objects which may be cleaned up using Release() or Close() may equally be CBase-derived heap-based objects, such as the one shown below, or R class objects which typically reside on the stack, as shown in a later example. As I’ll describe, the CleanupDeletePushL() function should only apply to heap-based objects, so this function has a pointer argument.

CleanupDeletePushL(T& aRef)

Here’s the implementation from e32base.inl:

// Template class CleanupDelete template <class T>

inline void CleanupDelete<T>::PushL(T* aPtr) {CleanupStack::PushL(TCleanupItem(&Delete,aPtr));}

template <class T>

void CleanupDelete<T>::Delete(TAny *aPtr) {delete STATIC_CAST(T*,aPtr);}

template <class T>

inline void CleanupDeletePushL(T* aPtr) {CleanupDelete<T>::PushL(aPtr);}

USING THE CLEANUP STACK WITH NON-CBase CLASSES

43

As you can see, leave processing or a call to PopAndDestroy() calls delete on an object added to the cleanup stack using this method. A call is thus made to the destructor of the object and the heap memory associated with the object is freed. It is similar to using the CleanupStack::PushL() overload which takes a CBase pointer, and is useful when an M class-derived pointer must be placed on the cleanup stack.

Calling CleanupStack::PushL() and passing in an M class pointer to a heap-based object invokes the overload which takes a TAny* parameter. For this overload, the TCleanupItem constructed does not call the object’s destructor, as I discussed above. Instead, it simply attempts to free the memory allocated for the object and, in doing so, causes a panic (USER 42) because the pointer does not point to the start of a valid heap cell.

This is because a concrete class that derives from an M class also inherits from another class, for example CBase, or a derived class thereof. The mixin pointer must be the second class in the inheritance declaration order if destruction of the object is to occur correctly, through the CBase virtual destructor. This means that when you access the object through the M class interface, the pointer has been cast to point at the M class sub-object which is some way into the allocated heap cell for the object. The memory management code cannot deduce this and panics when it cannot locate the appropriate structure it needs to free the cell back to the heap.

To resolve this, you should always use CleanupDeletePushL() to push M class pointers onto the cleanup stack, if the M class will be cleaned up through a virtual destructor. This creates a TCleanupItem for the object which calls delete on the pointer, using its destructor to cleanup the object correctly.

CleanupClosePushL(T& aRef)

If an object is pushed onto the cleanup stack using CleanupClosePushL(), leave processing or a call to PopAndDestroy() calls Close() on it. As an example, it can be used to make a stack-based handle to the file server leave-safe, closing it in the event of a leave:

void UseFilesystemL()

{

RFs theFs;

User::LeaveIfError(theFs.Connect());

CleanupClosePushL(theFs);

... // Call functions which may leave

CleanupStack::PopAndDestroy(&theFs);

}