摘要:错误处理和清理工作对于Symbian OS环境下的开发显得尤为重要。错误处理(error handle)实际上是为了编写出更可靠的程序,而清理(cleanup)框架则是Symbian OS的编程基础,正因为如此,必须保证错误处理和清理非常有效且易于实现。
OOM(out-of-memory)错误在Symbian OS环境下则不得不谈。目前的台式机内存的容量越来越大,加上更大硬盘上创建的虚拟内存,而且使用者可能进行经常性的重启。在这样的情况下,内存耗尽很少发生。而在Symbian OS的环境下,RAM少到只有4MB,一般也不会超过16MB,使用时经常要求不必重启。所以我们在Symbian OS环境下开发要注意以下问题:
必须高效编程,使程序不会浪费RAM。 必须尽快释放资源,因为不可能不释放资源,让运行程序消耗越来越多的RAM。 必须处理内存不足错误。在Symbian OS中这类错误可以随机发生。 若出现内存不足错误,导致某些操作停止,一定不能丢失用户数据。 若某个要分配若干个资源的操作中途发生了内存不足,必须清理所有这些资源。
实际上Symbian OS的错误处理和清理框架并不只对OOM错误有效,很多其他操作因为其环境条件可能失败,比如读写文件、打开文件,在通信会话中发送和接受数据。也可以使用错误处理和清理框架来处理。
下面列举一些用于处理内存不足错误和测试OOM的工具:
最普通的调试键 常用的Ctrl+Alt+Shift+(A使用堆单元、B文件服务器资源、C窗口服务器资源) 堆检查工具,用来检查函数分配的资源是否释放 C++的析构函数来销毁对象 堆失败工具,故意产生错误 异常退出机制,用来指示错误。其中一个基本函数User::Leave() 清理栈 异常捕获装置:用陷阱捕获异常退出的过程,类似与C++中的try-catch机制 CBase类,所有C类的基类,由清理栈来识别,包含一个C++析构函数 双阶段构造函数 命名约定
以上所述的工具接下来部分会做一介绍: 堆检查: 主要是使用宏_UHEAP_MARK和_UHEAP_MARKEND。 宏_UHEAP_MARKEND主要在析构函数中调用,此时在程序中任何位置如果包含了宏_UHEAP_MARK,则可以检查中间的堆操作是否平衡,就是说如果堆单元的数目和调用_UHEAP_MARK时数量不同,程序就会出错。 析构函数 想大家都不陌生,只是简单提一下 程序中使用过的对象,并不是所有的都要在析构函数中去释放,所销毁的只是自己所拥有的对象,也就不包括临时生成的局部变量。 堆失败工具 这个工具主要用来处理内存不足的错误,有个名为memorymagic的应用程序就是这个作用的,但不是所有的symbian系统都有相应的这个应用 具体使用方法可以到symbian的官方网站上下载这个东东,里边有相应的使用说明。 异常退出机制 当然就是使用User::Leave()来根据不同的错误码(请参照本blog中的Symbian错误码)异常退出,User::Leave()函数可以让活动函数的运行终止,并且接着终止所有调用函数。 清理栈 清理栈解决如下问题:清理栈在堆上分配的,但是指向它的唯一指针却是自动变量的对象。如果分配对象的函数异常退出,则需要清理对象。例程 case EMagicCmd: { CX* x = new (ELeave) CX; CleanupStack::PushL(x); x->UseL(); CleanupStack::PopAndDestroy(x); } 上述例程无论UseL()运行是否异常,程序都可以正常退出,因为作为异常处理的一部分,弹出并销毁清理栈上的所有对象是必须完成的。当然你也可以这么做 case EMagicCmd: { CX* x = new (ELeave) CX; TRAPD(error,x->UseL()); if(error) {delete x; User::Leave(error); } delete x; }
注意:没有必要不要使用清理栈 我们只需要使用清理栈来阻止越过对象的析构函数,如果该对象的析构函数一定会被调用,那么就一定不要使用清理栈。 一般来说类的成员变量往往都可以被类自身的析构函数销毁,所以绝对不应该把成员变量推入清理栈,避免二次删除!
两阶段构造 清理栈用于保存指向基于堆的对象的指针,以便发生异常退出时进行清理。这意味着,必须有机会来把对象推入清理栈。这个问题就可以利用两阶段构造来解决。
首先请记住C++的构造函数不应该包含任何可能异常退出的函数。例程: class CY : public CBase { public: CY(); ~CY(); public: CX* ix; CY:CY() { ix = new(ELeave) CX; } CY:~CY() { delete ix; } } 调用 CY* y = new(ELeave) CY; CleanupStack::PushL(y); ... CleanupStack::PopAndDestroy(y); 这个时候在分配CY的过程中,同时要分配一个CX,由于构造函数CY()没有异常处理机制,因此一旦分配CX失败,则得到内存泄漏错误,因此需要利用一个完全独立的函数来实现这个。于是就定义了ConstructL() class CY : public CBase { public: ~CY(); static CY* NewL(); static CY* NewLC(); void ConstructL(); public: CX* ix; CY:~CY() { delete ix; } }
void CY:ConstructL() { ix = new(ELeave)CX; } 调用改为: CY* y = new(ELeave) CY; CleanupStack::PushL(y); y->ConstructL(); ... CleanupStack::PopAndDestroy(y); 很显然这时ConstructL()能够安全清理。
一些经验小节: 对象不可分配两次, 不可删除两次。 delete ib; //加上 ib = NULL; //加上 ib = new CB; 不要删除非拥有对象 决不要从C++构造函数中异常退出。 使用好命名规范,帮助自己理解程序。 用好清理栈。 用好宏TRAPD()同User:Leave()。 ----------------------------------------------------------- 总之,Symbian OS下的错误处理和清理是异常复杂和难以琢磨的,只有用我们细心加耐心去慢慢克服。
--风小云原创 
|