Symbian系统中的异常处理和清理机制
一、介绍
在Symbian系统中处理异常的方法与传统的标准C++中的try,catch机制有很大的不同。
Symbian系统提供了一些特殊的清理机制来解决这个问题。系统提供了TRAP/TRAPD宏
、CleanupStack类以及一些约定俗成的方法(例如Leave函数,这些在我们了解了
系统的清理机制以后就会清楚它们的工作原理了)。Symbian系统直到9.1版本才开始
支持标准C++的try,catch异常捕获机制。但是Symbian系统的开发文档中建议开发者们
使用Symbian自己的清理机制。
二、Leave函数和TRAP宏
(1)关于函数名的一些解释
a.如果函数名是以L结尾的,那么意味着这个函数在运行时可能会Leave(抛出异常)
b.如果函数名是以LC结尾的,那么意味者这个函数在运行时可能会Leave(抛出异常),
该函数在运行时将向清理栈压入一个对象。(发生异常时是需要调用者手工释放的)。
c.如果函数名是以LD结尾的,那么意味着这个函数将在运行时可能会Leave(抛出异常),
同时该函数会向清理栈压入一个对象,如果函数运行时发生了异常,那么它自己会弹出
清理栈的对象,并且释放该对象申请的资源。
(2)注意事项
不要在TRAP和TRAPD宏中使用LC结尾的函数,因为在TRAP宏里面标记了清理栈,它会自动
调用传入的函数,并且它希望在函数调用结束后,函数能够把清理栈中的所有本次调用
产生的对象都弹出。而LC结尾的函数将向清理栈压入一个生成的对象,该对象应该由该
函数的调用者弹出,因此当我们使用TRAP/TRAPD宏调用以LC结尾的函数的话,在调用结束
后清理栈中仍然有一个对象不能够被销毁。这样就会引发E32User Panic 71的严重错误。
三、清理栈和TRAP/TRAPD宏
系统中每个分配了资源的可执行单元(或者线程)都有它自己的清理栈和最高级别的
TRAP/TRAPD宏来做异常处理和一些退出后的善后工作。为了更进一步的了解这些操作
我们来看下面的代码:
void DoTheThingsForMeL(CConsoleBase* aConsole)
{
//Do something here.
}
void DoExampleL()
{
CConsoleBase* console;
// Make the console and push it on the cleanup stack.
console = Console::NewL(_L("Console"), TSize( KConsFullScreen, KConsFullScreen));
CleanupStack::PushL(console);
DoTheThingsForMeL(console);
CleanupStack::PopAndDestroy(console);
}
TInt E32Main()
{
__UHEAP_MARK;
//Create a cleanup stack
CTrapCleanup* cleanup = CTrapCleanup::New();
//Call some Leaving methods inside TRAP
TRAPD(error, DoExampleL());
__ASSERT_ALWAYS(!error, User::Panic(KAPConsoleTest, error));
//Destroy cleanup stack
delete cleanup;
__UHEAP_MARKEND;
return 0;
}
如果在main()函数中不使用TRAPD调用DoExample()函数的话,程序会发生PANIC错误,
EClnPushAtLevelZero返回值,并且有如下错误信息 "any operation on CleanupStack
expects that there is at least one TRAP marker",
如果不使用TRAP的话,CCleanup::PushL()将会报错,返回EClnPushAtLevelZero。
如果你看了TRAP和TRAPD宏的定义,你就可以理解,在执行传入的函数之前,
它会标记清理栈,然后执行传入的函数,执行完毕之后,它会检查在本次调用
TRAP或TRAPD宏期间,在清理栈内是否还有剩余的对象。如果有,宏就会发生PANIC
错误,错误代码是E32user-CBase71。与此类似,如果我们在传入的函数中从
清理栈中Pop或者PopAndDestroy太多的对象,宏也会检查出来,并且引发PANIC错误,
错误代码是E32user-CBase63,同样,如果你根本就不创建清理栈,(如果你
注释掉 CTrapCleanup* cleanup = CTrapCLeanup::New())这句)那么同样会
得到CleanupStack::PushL()的PANIC错误,错误代码是EClnNoTrapHandlerInstalled。
记住——TRAP和TRAPD宏是要标记清理栈的,所以程序中一定要创建清理栈。
但是使用TRAP/TRAPD宏和创建清理栈并不是编程所必须的,如果开发过程中没有使用
到任何公共资源(不会造成Leave或者异常的操作),那么完全可以不必理会这些宏
和清理栈。例如下面的代码:
TInt E32Main()
{
TInt array[10];
for(TInt i=0; i<10; i++){
array[ i ]=i+1;
}
return 0;
}
就完全不需要清理栈和TRAP以及TRAPD宏。
四、不好的编码习惯
在你的应用开发里面,不要使用下面的编码方式:
TRAPD(err, SomeLeavingFunL)
if (err==KErrNone)
{
...
TRAPD(err, SomeLeavingFunL)
...
}
...
User::LeaveIfError(err);
在这段代码中,由于使用了两次TRAPD我们会失去第一次调用TRAPD的err的值,
所以最好每次调用TRAP和TRAPD宏的时候,采用不同的变量保存错误返回值。
同时因为TRAP/TRAPD的执行效率比较低。最好在同一个函数体里面不要多次使用该宏,
尽可能少的使用TRAP/TRAPD宏,例如我们可以把下面的代码重新写一下:
TInt SomeFunction() {
TRAPD(err1, CallLeavingMethodL());
TRAPD(err2, CallLeavingMethodL());
TRAPD(err3, CallLeavingMethodL());
TRAPD(err4, CallLeavingMethodL());
return err1 || err2 || err3 || err4;
}
上面的代码可以这样进行改写,效率会提高很多。
void SomeFunctionL() {
CallLeavingMethodL();
CallLeavingMethodL();
CallLeavingMethodL();
CallLeavingMethodL();
}
TInt SomeFunction() {
TRAPD(err, SomeFunctionL());
return err;
}
五、线程和异常处理
我们可以通过RThread::Create() API来创建线程,这个API如下有两种形式:
TInt Create(const TDesC& aName, TThreadFunction aFunction, TInt aStackSize,
TInt aHeapMinSize, TInt aHeapMaxSize, TAny* aPtr,TOwnerType aType = EOwnerProcess);
TInt Create(const TDesC& aName, TThreadFunction aFunction, TInt aStackSize,
RAllocator* aHeap, TAny* aPtr, TOwnerType aType = EOwnerProcess);
在默认的情况下新创建的线程会有属于它自己的堆。它和它的父线程并不共享线程堆,
当然了我们也可以手工指定他们共享同一个堆。
使用第一种形式的Create() API函数,我们不能创建一个共享其父线程堆的子线程。
为了实现共享的要求,我们必须用第二种形式的Create() API。我们只需要向aHeap
参数中传入NULL即可,这时,子线程就会使用父线程的堆来为自己分配必要的资源。
当你创建了一个共享父线程堆的子线程时,你必须要切记再退出的时候释放了全部
申请的资源。否则的话,即使子线程退出了,那么它所申请资源仍然不会释放掉,
如果父线程长期运行,那么由该子线程申请的资源父线程也无法使用,这就导致
了内存泄漏。
当创建了一个有自己的堆的线程的时候,即使忘记了释放全部分配的内存,当该线程
退出的时候,它会释放全部申请的内存,甚至包括堆中的分配的内存。(但是即使
这样,也还是建议在退出之前释放掉所有申请的资源)当你创建线程的时候,
以下几点是需要牢记的:
(1)在你的线程里,如果使用了TRAP/TRAPD宏的话,他们将操作最上层的Leave函数
(2)在你的线程里,如果要使用TRAP/TRAPD宏或者使用类似的Leave函数的时候,
你已经创建了自己的清理栈。
(3)如果你要使用异步请求的话(例如活动对象(Active Object)),必须已经
为该线程安装一个调度器(scheduler)
现在让我们假定我们要创建一个线程,它使用了清理栈,需要堆多种异步请求
做出响应。 ThreadEntryFunction,入口函数应该先进行以下的操作,才能
进行具体的动作:
TInt ThreadEntryMethod( TAny* aParam )
{
TInt retVal = KErrNoMemory;
// Create a Cleanup Stack for this Thread
CTrapCleanup* cleanupStack = CTrapCleanup::New();
if( cleanupStack )
{
// Create a Scheduler for this Thread and Install it
CActiveScheduler* scheduler = new CActiveScheduler;
if( scheduler )
{
CActiveScheduler::Install( scheduler );
TRAP( retVal, retVal = DoSomeThingForMeL( aParam ) );
delete scheduler;
}
delete cleanupStack;
}
return retVal;
}
上面的代码是一个新创建的线程的入口函数。这里我们关注的是所有该
线程执行时候的全部必要的操作。在这里,我用CTrapCleanup::New()
方法创建了清理栈,然后创建和安装了一个调度器(ActiveScheduler),
然后,我用TRAP宏调用一个可能会发出异常的函数,然后在DoSomeThingForMeL()
函数中对异常进行处理。因为我创建了清理栈,所以可以使用TRAP宏进行
异常的操作和处理。
一、介绍
在Symbian系统中处理异常的方法与传统的标准C++中的try,catch机制有很大的不同。
Symbian系统提供了一些特殊的清理机制来解决这个问题。系统提供了TRAP/TRAPD宏
、CleanupStack类以及一些约定俗成的方法(例如Leave函数,这些在我们了解了
系统的清理机制以后就会清楚它们的工作原理了)。Symbian系统直到9.1版本才开始
支持标准C++的try,catch异常捕获机制。但是Symbian系统的开发文档中建议开发者们
使用Symbian自己的清理机制。
二、Leave函数和TRAP宏
(1)关于函数名的一些解释
a.如果函数名是以L结尾的,那么意味着这个函数在运行时可能会Leave(抛出异常)
b.如果函数名是以LC结尾的,那么意味者这个函数在运行时可能会Leave(抛出异常),
该函数在运行时将向清理栈压入一个对象。(发生异常时是需要调用者手工释放的)。
c.如果函数名是以LD结尾的,那么意味着这个函数将在运行时可能会Leave(抛出异常),
同时该函数会向清理栈压入一个对象,如果函数运行时发生了异常,那么它自己会弹出
清理栈的对象,并且释放该对象申请的资源。
(2)注意事项
不要在TRAP和TRAPD宏中使用LC结尾的函数,因为在TRAP宏里面标记了清理栈,它会自动
调用传入的函数,并且它希望在函数调用结束后,函数能够把清理栈中的所有本次调用
产生的对象都弹出。而LC结尾的函数将向清理栈压入一个生成的对象,该对象应该由该
函数的调用者弹出,因此当我们使用TRAP/TRAPD宏调用以LC结尾的函数的话,在调用结束
后清理栈中仍然有一个对象不能够被销毁。这样就会引发E32User Panic 71的严重错误。
三、清理栈和TRAP/TRAPD宏
系统中每个分配了资源的可执行单元(或者线程)都有它自己的清理栈和最高级别的
TRAP/TRAPD宏来做异常处理和一些退出后的善后工作。为了更进一步的了解这些操作
我们来看下面的代码:
void DoTheThingsForMeL(CConsoleBase* aConsole)
{
//Do something here.
}
void DoExampleL()
{
CConsoleBase* console;
// Make the console and push it on the cleanup stack.
console = Console::NewL(_L("Console"), TSize( KConsFullScreen, KConsFullScreen));
CleanupStack::PushL(console);
DoTheThingsForMeL(console);
CleanupStack::PopAndDestroy(console);
}
TInt E32Main()
{
__UHEAP_MARK;
//Create a cleanup stack
CTrapCleanup* cleanup = CTrapCleanup::New();
//Call some Leaving methods inside TRAP
TRAPD(error, DoExampleL());
__ASSERT_ALWAYS(!error, User::Panic(KAPConsoleTest, error));
//Destroy cleanup stack
delete cleanup;
__UHEAP_MARKEND;
return 0;
}
如果在main()函数中不使用TRAPD调用DoExample()函数的话,程序会发生PANIC错误,
EClnPushAtLevelZero返回值,并且有如下错误信息 "any operation on CleanupStack
expects that there is at least one TRAP marker",
如果不使用TRAP的话,CCleanup::PushL()将会报错,返回EClnPushAtLevelZero。
如果你看了TRAP和TRAPD宏的定义,你就可以理解,在执行传入的函数之前,
它会标记清理栈,然后执行传入的函数,执行完毕之后,它会检查在本次调用
TRAP或TRAPD宏期间,在清理栈内是否还有剩余的对象。如果有,宏就会发生PANIC
错误,错误代码是E32user-CBase71。与此类似,如果我们在传入的函数中从
清理栈中Pop或者PopAndDestroy太多的对象,宏也会检查出来,并且引发PANIC错误,
错误代码是E32user-CBase63,同样,如果你根本就不创建清理栈,(如果你
注释掉 CTrapCleanup* cleanup = CTrapCLeanup::New())这句)那么同样会
得到CleanupStack::PushL()的PANIC错误,错误代码是EClnNoTrapHandlerInstalled。
记住——TRAP和TRAPD宏是要标记清理栈的,所以程序中一定要创建清理栈。
但是使用TRAP/TRAPD宏和创建清理栈并不是编程所必须的,如果开发过程中没有使用
到任何公共资源(不会造成Leave或者异常的操作),那么完全可以不必理会这些宏
和清理栈。例如下面的代码:
TInt E32Main()
{
TInt array[10];
for(TInt i=0; i<10; i++){
array[ i ]=i+1;
}
return 0;
}
就完全不需要清理栈和TRAP以及TRAPD宏。
四、不好的编码习惯
在你的应用开发里面,不要使用下面的编码方式:
TRAPD(err, SomeLeavingFunL)
if (err==KErrNone)
{
...
TRAPD(err, SomeLeavingFunL)
...
}
...
User::LeaveIfError(err);
在这段代码中,由于使用了两次TRAPD我们会失去第一次调用TRAPD的err的值,
所以最好每次调用TRAP和TRAPD宏的时候,采用不同的变量保存错误返回值。
同时因为TRAP/TRAPD的执行效率比较低。最好在同一个函数体里面不要多次使用该宏,
尽可能少的使用TRAP/TRAPD宏,例如我们可以把下面的代码重新写一下:
TInt SomeFunction() {
TRAPD(err1, CallLeavingMethodL());
TRAPD(err2, CallLeavingMethodL());
TRAPD(err3, CallLeavingMethodL());
TRAPD(err4, CallLeavingMethodL());
return err1 || err2 || err3 || err4;
}
上面的代码可以这样进行改写,效率会提高很多。
void SomeFunctionL() {
CallLeavingMethodL();
CallLeavingMethodL();
CallLeavingMethodL();
CallLeavingMethodL();
}
TInt SomeFunction() {
TRAPD(err, SomeFunctionL());
return err;
}
五、线程和异常处理
我们可以通过RThread::Create() API来创建线程,这个API如下有两种形式:
TInt Create(const TDesC& aName, TThreadFunction aFunction, TInt aStackSize,
TInt aHeapMinSize, TInt aHeapMaxSize, TAny* aPtr,TOwnerType aType = EOwnerProcess);
TInt Create(const TDesC& aName, TThreadFunction aFunction, TInt aStackSize,
RAllocator* aHeap, TAny* aPtr, TOwnerType aType = EOwnerProcess);
在默认的情况下新创建的线程会有属于它自己的堆。它和它的父线程并不共享线程堆,
当然了我们也可以手工指定他们共享同一个堆。
使用第一种形式的Create() API函数,我们不能创建一个共享其父线程堆的子线程。
为了实现共享的要求,我们必须用第二种形式的Create() API。我们只需要向aHeap
参数中传入NULL即可,这时,子线程就会使用父线程的堆来为自己分配必要的资源。
当你创建了一个共享父线程堆的子线程时,你必须要切记再退出的时候释放了全部
申请的资源。否则的话,即使子线程退出了,那么它所申请资源仍然不会释放掉,
如果父线程长期运行,那么由该子线程申请的资源父线程也无法使用,这就导致
了内存泄漏。
当创建了一个有自己的堆的线程的时候,即使忘记了释放全部分配的内存,当该线程
退出的时候,它会释放全部申请的内存,甚至包括堆中的分配的内存。(但是即使
这样,也还是建议在退出之前释放掉所有申请的资源)当你创建线程的时候,
以下几点是需要牢记的:
(1)在你的线程里,如果使用了TRAP/TRAPD宏的话,他们将操作最上层的Leave函数
(2)在你的线程里,如果要使用TRAP/TRAPD宏或者使用类似的Leave函数的时候,
你已经创建了自己的清理栈。
(3)如果你要使用异步请求的话(例如活动对象(Active Object)),必须已经
为该线程安装一个调度器(scheduler)
现在让我们假定我们要创建一个线程,它使用了清理栈,需要堆多种异步请求
做出响应。 ThreadEntryFunction,入口函数应该先进行以下的操作,才能
进行具体的动作:
TInt ThreadEntryMethod( TAny* aParam )
{
TInt retVal = KErrNoMemory;
// Create a Cleanup Stack for this Thread
CTrapCleanup* cleanupStack = CTrapCleanup::New();
if( cleanupStack )
{
// Create a Scheduler for this Thread and Install it
CActiveScheduler* scheduler = new CActiveScheduler;
if( scheduler )
{
CActiveScheduler::Install( scheduler );
TRAP( retVal, retVal = DoSomeThingForMeL( aParam ) );
delete scheduler;
}
delete cleanupStack;
}
return retVal;
}
上面的代码是一个新创建的线程的入口函数。这里我们关注的是所有该
线程执行时候的全部必要的操作。在这里,我用CTrapCleanup::New()
方法创建了清理栈,然后创建和安装了一个调度器(ActiveScheduler),
然后,我用TRAP宏调用一个可能会发出异常的函数,然后在DoSomeThingForMeL()
函数中对异常进行处理。因为我创建了清理栈,所以可以使用TRAP宏进行
异常的操作和处理。