对于 Symbian OS 中使用的 CleanupStack 机制,在这里不作好坏的评价,既然选择了在 Symbian 平台上开发,那最重要的就是了解它的机制,掌握并高效的利用好它。
对于在 Symbian 平台上开发 GUI 或者 Server 程序, CleanupStack 已由框架创建,用户可直接使用 CleanupStack::PushL() 、 CleanupStack::Pop() 等方法来控制可能的异常。在这样的框架下,用户无法了解 CleanupStack 是如何被创建,是如何工作的。
当在 Symbian 平台上开发 Console 程序或者使用多线程时,异常清理栈 CleanupStack 就必须由用户自己创建和维护。 Symbian 封装了其中的核心机制,使得用户可以非常方便的创建 CleanupStack 清理栈并使用它。典型的代码如下:
TInt ThreadEntry(TAny *arg) // 用户线程的入口函数
{
TInt retCode = 0; // 函数异常退出的错误代码
CTrapCleanup* cleanupstack = CTrapCleanup::New(); // 创建 CleanupStack 清理栈
if(cleanupstack == NULL)
return -1;
TRAP(retCode,Fun_EntryL()); // 捕获 Fun_EntryL() 异常退出
If(retCode != KerrNone)
{
//Handle_Error();
}
delete cleanupstack;
return retCode;
}
Void Fun_EntryL()
{
CTestObj *obj = CTestObj::NewL(); // 创建 CTestObj 时可能 Leave
CleanupStack::PushL(obj);
Obj->FuncMayLeaveL(); // FuncMayLeaveL() 方法调用可能 Leave
CleanupStack::PopAndDestroy();
}
上述主要的方法已做大致的说明,接下去具体分析 CleanupStack 清理栈创建和工作过程。 CleanupStack 类只是 Symbian OS 提供的针对清理栈的静态类,也就是工具类, Symbian OS 中真正的清理栈功能是由 CCleanup 实现的。但如上述代码,我们在创建清理栈时并没有直接创建 CCleanup 对象,而使用 CTrapCleanup::New() 方法创建了一个 CTrapCleanup 对象,这其中就是核心所在。
当调用 CTrapCleanup::New() 方法时,真正做了什么?首先,要使用清理栈,则必须创建 CCleanup 对象;其次在 CTrapCleanup 中创建了一个 TCleanupTrapHandler 对象。在创建了这些对象后,调用 User::SetTrapHandler() 将生成的 TCleanupTrapHandler 安装到当前线程中以备后用。
在用户线程中,当调用可能 Leave 的方法时,必须用 TRAP/TRAPD 宏加以捕获。 TRAP/TRAPD 宏调用 TCleanupTrapHandler::Trap() 以标志开始异常捕获,当被 TRAP 宏监视的函数 Leave 时,调用 TCleanupTrapHandler::Leave() 方法控制其 CCleanup 对象完成之前已压入清理栈的对象;当被 TRAP 监视的函数正常时,调用 TCleanupTrapHandler::UnTrap() 方法取消异常捕获。
同样,在可能 Leave 的方法中,如上述的 Fun_EntryL() ,当调用 CleanupStack::PushL(obj) 方法时,其内部是通过 User::TrapHandler() 获得当前线程中已安装的 TCleanupTrapHandler 对象,然后通过 TCleanupTrapHandler::Cleanup() 获得清理栈类,最后由清理栈类真正完成压栈、出栈和异常时栈内对象的内存释放。
总结,清理栈的工作是以一个 TRAP/TRAPD 为单位的,在被某个 TRAP 宏监视的代码段内压入清理栈的对象,当出现异常 Leave 时,这些对象都能通过清理栈完成内存释放,不会导致内存泄露。但在不同 TRAP 宏压入的对象,在上述情况下是无法释放的,这点需要注意。
前面一节主要描述了 Symbian OS 中清理栈 CleanupStack 的核心基础结构及工作线路,以在用户线程中创建一个 CleanupStack 对象为例,详细分析了 CleanupStack 创建、调用的内部工作机制。本节将说明 CleanupStack 类针对不同对象,提供的不同方法,在发生 Leave 时的不同动作。
Symbian OS 提供用户操作清理栈的接口通过 CleanupStack 类展示,全部为静态方法。将对象压入清理栈的方法有 CleanupStack::PushL(CBase*) 、 CleanupStack::PushL(TAny*) 和 CleanupStack::PushL(TCleanupItem &) 三种。从方法的不同传入参数可基本看出,针对不同的对象类型,压栈方法将调用不同的 PushL 重载方法来实现不同对象的入栈,当然其目的不在于如何进栈,而在于当发生 Leave 时,之前被压入栈的对象将做不同的处理,接下去就将详细说明不同类对象 Leave 时的不同处理。
当对象继承自 Cbase 类时,将该对象压入清理栈时会调用 CleanupStack::PushL(CBase*) 重载方法。
class CHeapClass : public CBase
{
public :
~CHeapClass ();
static CHeapClass* NewL ();
static CHeapClass* NewLC ();
void FuncL();
private :
CHeapClass ();
void ConstructL ();
private:
TUint8* iBuf;
};
void CheapClass:: ConstructL()
{
……
iBuf = new TUint8[100];
……
}
CheapClass:: ~CHeapClass ()
{
if(iBuf != NULL)
delete[] iBuf;
}
通常,当程序中需用到指向堆对象的局部变量时,为防止内存泄露,需在调用可能发生 Leave 的方法前,将该对象压入当前线程的清理栈中,如下:
void LocalFunc()
{
CheapClass *obj = CheapClass::NewL();
CleanupStack::PushL(obj);
Obj-> FuncL();
CleanupStack::PopAndDestroy();
}
当调用代码 FuncL 出现 Leave 或者调用 CleanupStack::PopAndDestroy() 时,根据清理栈机制,此时就会释放相应对象。由于之前压入的对象 obj 是 CBase 的派生类,该类的特点就是具有需析构函数。所以,当发生 Leave 时,清理栈类调用 delete 方法来删除该对象, delete 方法相应地能正确地调用到该派生类的析构函数,这样就能完成释放该对象及该对象所占有的资源。
对于非 CBase 派生类,当压栈时调用 CleanupStack::PushL(TAny*) 方法。此时,当发生 Leave 或者调用 CleanupStack::PopAndDestroy() 时,清理栈内部所做的工作不同于上述情况。清理栈在清理对象时,只是简单的调用 User::Free() 来释放该对象。该重载方法适合没有析构函数的类对象 ( 该对象没有自己的独占资源需要释放 ) ,因为调用 User::Free() 方法不会导致类析构函数被调用。
对于特殊情况,当调用出现 Leave 时,代码不仅仅要做简单的类对象释放,很明显上述方法都无法满足。 Symbian OS 中提供如 CleanupClosePushL , CleanupDeletePushL 和 CleanupReleasePushL 等方法,来补充适合非类对象内存释放的情况。对于这些方法的调用,其核心就是清理栈的第三种接口 CleanupStack::PushL(TCleanupItem &) 的应用。
TCleanupItem(TCleanupOperation anOperation)
当将一个TcleanupItem 对象压入清理栈后,程序调用出现Leave 或者调用
CleanupStack::PopAndDestroy() 时,在退出 TRAP 前,代码有机会在 TcleanupItem
对象中定义的TcleanupOperation 方法中先得到异常清理,TcleanupOperation 方法定义如下:
typedef void(* TCleanupOperation)(TAny*)
在自定义的TcleanupOperation 方法中,用户有机会对程序异常做更多复杂和自定义的异常处理,而不是简单地只调用delete 方法或者User::Free() 来完成内存释放功能,在该方法,用户可以同时释放内存,释放该对象所占有的Symbian 资源等。
Symbian OS
提供的
CleanupReleasePushL() 等方法,封装了其中内部工作机制,使得用户可以方便的完成相应自定义清理方法。
class RTest ;
{
public :
void Release();
}
RTest testObj;
CleanupReleasePushL (testObj);
…… //Some Leave Func
CleanupStack::PopAndDestroy();
当调用 CleanupReleasePushL (testObj) 时,内部调用了工具类 CleanupRelease:: PushL()
方法,创建了一个
TCleanupItem 对象,并用对象 testObj 的 void Release() 方法初始化 TCleanupItem 对象的 TcleanupOperation
方法,同时将该
TCleanupItem 压入清理栈。当程序 Leave 或者调用 CleanupStack::PopAndDestroy() 时,对象 testObj 的 void Release() 方法将被调用,用户可在该方法中做相应的异常处理。
总结,当通过 CleanupStack::PushL(CBase*) 将 CBase 派生类压入清理栈后,程序 Leave 时,该类的析构函数能被及时调用,能有机会释放该对象占有的内存资源;当通过 CleanupStack::PushL(TAny*) 方法将非 CBase 类对象压入清理栈后, 程序 Leave 时,清理栈仅简单的调用 User::Free() 方法释放该对象,而不会导致析构函数被调用;当通过 CleanupStack::PushL(TCleanupItem &) 方法将 TCleanupItem 对象压入清理栈后,程序 Leave 时, TCleanupItem 对象中定义的清理函数将被调用,可完成一些复杂情况下的异常清理。
开发提示:
如上节所描述, TRAP 宏有较大的内存调用开销。所以,在代码中尽量控制频繁使用 TRAP 宏。在一些逻辑上不可分割的操作上,可利用 CleanupStack::PushL(TCleanupItem &) 重载方法,在程序发生 Leave 时,在自定义的异常处理方法中控制程序逻辑,而不采用 TRAP 宏判断。