骚扰精灵

这几天迷上了Symbian开发。。想做一个骚扰精灵玩玩。。

搞了两天了。。还有些小问题。。

问题:RCall类中的Dial函数是阻塞的。导致另一个监视线路状态的活动对象不能及时得到执行

憋了一天应该是这个问题导致的。。

两个活动对象。一个负责Dial,一个负责监视Line状态。。如果为EStatusConnecting则挂断。两个活动分开用很好。。都很正常。。可放在一起。。就不正常了。。挂不断电话。。。我想应该是Dial是阻塞函数导致的。。PO Dial函数。。非得打通不可。。

看来明儿得搞搞多线程了。。心得完毕。回宿舍睡觉!

乎乎。。

s60 Capabilities(Symbian OS9)

UIQ3 Capabilities(Symbian OS9)



能力(Capabilities)

从Symbian 9开始平台安全性的问题被引入,这意味着,硬件设备只能有限的访问安全的数据和软件。

因此在S60第三版和UIQ3的设备上,开发者开发的程序需要一些授权才能够访问平台。这种授权被称之为能力。这些能力被定义为授权应用,因为某个API被限制使用,除非能力(capability )授予该程序使用这些属性。

这里有3种主要的能力:

1. TCB(底层处理信任)

2. USER(用户层)

3. 系统能力



TCB : “底层处理信任(Trusted computing Base )”

它由一些底层的系统属性组成,像文件系统中的目录。这些能力(Capabilities)是不授予第三方应用程序的,他们只被授权给平台的制造商。

TCB包括操作系统的核心,文件服务和存储器管理单元等等。

USER(用户层)

网络服务:打电话、发短信、访问远程服务。

本地服务:这个能力包括:通过USB、红外、蓝牙设备发送和接受信息

读取用户数据:授权读取用户信息。系统服务和应用程序引擎就是这个级别。

写用户数据:授权写用户数据,同读取的服务一致。

区域:授权访问手机的某个区域。

用户环境:授权访问关于用户和环境的保密信息

系统能力(System Cabability)

These API’s under these capbilities has access to sensitive parts of the machine which due programatical errors may corrupt the phone’s Software and hardware. So these capabilities are granted only after symbian signed which after testing is done.

ALLFiles,SwEvent,WriteDeviceData,PowerMgmt,Drm, etc.

如何赋予能力

可以在MMP文件中,使用CAPABILITY 关键字来赋予能力

CAPABILITY ReadUserData   DiskAdmin

或者

CAPABILITY   ALL -ALLFiles Drm

//除了ALLFiles和Drm的能力

在项目中,我们可以通过相应的API可以使用相关的能力,例如:DataFileSave() 可以调用WriteDeviceData WriteUserData能力。

能力是访问敏感API权限的令牌。如果需要保护Symbian OS中的特定功能(API),则使用该功能的代码必须具有与之相关的能力。

需要访问能力受保护功能的代码必须通过授权过程才能获得使用能力的权限。

能力分类:

无限制:60%的API。

用户可以授权(在安装期间):ReadUserData,WriteUserData,NetworkServices,LocalServices,UserEnvironment。

Symbian Signed:用户可以授权的能力+Location,ReadDeviceData,WriteDeviceData,

PowerMgmt,SurroundingsDD,ProtServ,TrustedUI,SwEvent。

许可获得者/平台批准:Symbian Signed的能力+[DRM,TCB]厂商,[AllFiles,CommDD,DiskAdmin,MultiMediaDD,NetworkControl]能力请求。

[DRM,TCB]需要通过向厂商申请获得。

[AllFiles,CommDD,DiskAdmin,MultiMediaDD,NetworkControl]需要在www.symbiansigned.com上填写“能力请求表单”申请获得。

能力:

NetworkServices:用于使用移动网络,例如:拨打电话或发送文本消息。

LocalServices:用于通过USB、红外和蓝牙发送或接收消息。

ReadUserData:准许读取用户数据。系统服务器和应用引擎可以自由地对他们的数据施加这一限制。

WriteUserData:准许写入用户数据。系统服务器和应用引擎可以自由地对他们的数据施加这一限制。

Location:准许访问手机的位置信息。

UserEnvironment:准许访问用户及其附近环境的实时保密信息。

PowerMgmt:准许在系统中中断任何进程或者转换机器状态(关掉设备)。

SwEvent:准许生成或者捕获键盘以及笔输入事件。

ReadDeviceData:准许读取系统设备驱动数据。

WriteDeviceData:准许写入系统设备驱动数据。

SurroundingsDD:准许访问提供外围设备输入信息的逻辑设备驱动。

TustedUI:区分”normal”应用和”trusted”应用的UI。当一个”trusted”应用在屏幕上显示内容时,一个”normal”的应用不能伪造它。

ProtServ:准许服务器应用可以用一个受保护的名字进行注册。受保护的名字以”!”开头。

NetworkControl:准许修改或者访问网络协议控制。

MultimediaDD:准许对所有多媒体设备驱动(声音、摄像头等)的访问。

DRM:准许访问DRM保护的内容。

TCB:准许在终端中访问/sys以及/resource目录。

CommDD:准许访问通信设备驱动。

DiskAdmin:准许进行硬盘管理操作,例如格式化驱动器。

AllFiles:准许系统中的所有文件可见,而且还可对在/private下的文件进行写操作。

1.从上面可以看到”ReadUserData”,”WriteUserData”,”NetworkServices”,”LocalServices”,”UserEnvironment”这些能力是可以在安装期间由用户授权的,也就是自签名的程序可以使用.申请开发者证不一定需要ACS Publisher ID,只不过你有ACS Publisher ID的话可以为开发者证书申请到更多的能力.

3.使用某个能力需要在MMP文件中通过CAPABILITY语句指定.

文章出处:http://www.diybl.com/course/3_program/jdkf/2007926/74221.html

理解 Symbian C++ 的 NewL ConstructL NewLC ELeave[转]

这篇文章是我见到的解释最清楚的一篇。。。非常感谢作者

作者:yaodong     文章来源:http://blog.joycode.com/yaodong/articles/94824.aspx

初学Symbian开发,第一件感觉迷惑的事情是CleanupStack 第二件肯定是随处可见的NewL,NewLC,ConstructL。

这些函数的出现依然和内存泄漏有关,这是一种被称为两步构造的机制,英文叫Two-phase Construction。

我知道C++里面的 new 操作符实际上完成2件事,第一根据对象类的大小在堆上分配一块内存并获得指向内存的

指针,第二利用指针调用类的构造函数,最后把指针返回。

在Symbian上这样做是有隐患的,就是当你分配好了内存,但是调用构造函数的时候程序意外退出了,这样会造成

刚才分配的内存产生泄漏。只有那些放入CleanupStack的内存,在程序意外结束后会被释放,new 分配的内存在

调用构造函数之前还没有被放入CleanupStack呢。

Symbian的设计师为了解决这个问题,给所有开发者设计了一个编写程序的定式,这就是Two-phase Construction

具体是这样的:

把普通的new 操作分为2个步骤来进行,第一步只分配内存,当分配的内存被放入cleanupstack后,第二步进行构造。

但是在C++里 如何阻止编译器的new操作不调用构造函数呢?这个貌似不可以。

于是Symbian的设计者作了个规定,类在构造函数里不要做任何可能产生异常的操作,只能做那些绝对安全的事情,比如

简单的变量赋值,然后提供一个名字叫 ConstructL的函数,在这个函数里做所有类的初始化工作,当然包括那些危险

的可能导致异常的操作。

那么 Symbian的 类构造就变成了这样

比如 有一个叫 CFoo 的类,我想声明一个指针 p,创建一个CFoo的对象赋给 p
CFoo *p = new(ELeave) CFoo();

CleanupStack::PushL(p);

p->ConstructL();

CleanupStack::Pop();

这样写是不是有点太罗嗦?每个对象都要用4条语句创建,如果是频繁使用实在是太麻烦了。

于是Symbian的设计者又作了一个规定,每个类要实现一个NewL的static函数来完成上面的4条语句的工作
class CFoo

{

public:

static CFoo *NewL()

{

   CFoo *self = new(ELeave) CFoo();

   CleanupStack::PushL(self);

   self->ConstructL();

   CleanupStack::Pop();

   return self;

}

}

有了NewL以后,调用CFoo的类的程序简化了
CFoo *p = CFoo::NewL();

那么NewLC又是什么呢?和NewL有什么不同?

有些类是这样的,他们提供一些方法,需要在对象创建完成后执行,但是这些方法也是会产生异常的比如

CFoo 如果有一个方法叫 DoSomethingL()

那么程序可以这样写吗?
CFoo *p = CFoo::NewL();

p->DoSomethingL();

显然这样写是有问题的正确的写法是
CFoo *p = CFoo::NewL();

CleanupStack::PushL(p);

p->DoSomethingL();

CleanupStack::Pop();

天啊,又是4条语句,太麻烦了。要知道在NewL结尾我们刚刚把CFoo的指针从CleanupStack里拿出来,马上就又放了进去。

是不是可以简化呢,那好我们再节约2条语句

NewL去掉结尾的CleanupStack::Pop();

static CFoo *NewL()

{

   CFoo *self = new(ELeave) CFoo();

   CleanupStack::PushL(self);

   self->ConstructL();

   return self;

}

调用去掉CleanupStack::PushL

CFoo *p = CFoo::NewL();

p->DoSomethingL();

CleanupStack::Pop();

Symbian的设计者又规定了,具有以上行为的NewL 应该叫NewLC。表示指针返回后,没有从CleanupStack里取出,你可以继续调用一个危险的操作,在最后调用CleanupStack::Pop();

我发现 NewL 可以通过调用NewLC实现。

那么一个符合Symbian设计师的N条规定的类应该这样写

class CFoo

{

public:

static CFoo *NewLC()

{

   CFoo *self = new(ELeave) CFoo();

   CleanupStack::PushL(self);

   self->ConstructL();

   return self;

}

static CFoo *NewL()

{

   CFoo *self = NewLC();

   CleanupStack::Pop();

   return self;

}

virtual ~CFoo()

{

}

protected:

CFoo()

{

}

void ConstructL()

{

// ….

}

}

这里注意 CFoo的构造函数不能是Public的,为了防止使用者用new 或者在栈上创建对象。

析构函数要写成虚函数,这是纯C++问题,不明白的去看 More Effective C++

要说明一下,以上的写法是Symbian极力推荐的,但是不是硬性规定的,你只要保证没有内存泄漏

可以不这么写。

我个人还是推荐这样,这样的代码写Symbian程序的人都可以很好地理解。

最后说一下 new 之后为什么要有一个 (ELeave)。

new操作符是被Symbian重载过了,ELeave是给new的一个参数,他的意思是告诉new当无法分配内存时

程序就退出。比如内存不足的时候。

所以我们用了ELeave的话 就不用检查new 返回的指针了,能返回就一定是对的

如果出了错程序就结束掉了,new根本就不会返回。

NewL NewLC 是Symbian程序标志性的函数,所以有个Symbian开发的资源站点就叫www.newlc.com

[转载]Debugging console apps with 3rd edition emulator

Symbian console applications are handle for testing. Debugging them with the 2nd edition emulator was obvioius. With 3rd edition, there is a trick. Instead of the epoc.exe application, you need to run eshell.exe. This will bring up a command line. Enter the name of the executable at the prompt and away you go.



How does one configure the IDE to run a console applciation? With Carbide, select the “Debug…” memu item on the debug popup on the toolbar. This will bring up a dialog to configure the debug session. Select the “Main” tab. Edit the “Emulator or host application” from epoc.exe to eshell.exe.

Code Warrior is similar. It is in the setttings dialog but it is tucked away under Target->Runtime Settings. Update the “Host Application for Libraries & Code Resources” field.

另外,根据使用经验,直接打开eshell.exe仍然会启动有界面的模拟器,模拟器的界面会覆盖掉eshell的界面。可以通过快捷键Alt+Ctrl+Shift+T切换任务。模拟器中快捷键的用法可以在

http://www.symbian.com/developer/techlib/v70sdocs/doc_source/ToolsAndUtilities/Emulator/emulator/Debugging.guide.html

查到。此外,可以在启动eshell的时候加入参数”-dtextshell –“只启动eshell而不带有界面。这个设置可以在CodeWarrior的Target->Runtime Settings->Program Arguments更改。

Symbian异常三步曲之二:清除栈(CleanupStack) zz

一、为什么使用清除栈
清除栈主要是用来处理在异常退出发生时那些或许可以称之为被遗弃或泄漏的内存。

看下面的代码:

void UnsafeFunctionL()

{

       CClanger* clanger = new(ELeave) CClanger();

       clanger->InitializeL();

       ……..//略去

        delete clanger;

}


分析:一旦clanger->InitializeL()运行时发生异常,则clanger所指的堆内存将会泄漏,这个例子我们在三步曲之一中提到过。

那么有什么办法来处理这种潜在的错误呢?

我们很容易就想到,可以使用TRAPD捕获异常来进行堆内存的释放,即将上面代码进行如下修改:

void UnsafeFunctionL()

{

       CClanger* clanger = new(ELeave) CClanger();

       TRAPD(error,clanger->InitializeL());

       If(KErrNone != error)

{

               delete clanger;

}

       ……..//略去

       delete clanger;

}

也就是说通过TRAPD捕获异常,然后对异常错误码进行判断,如果确实发生异常了,那么就调用delete来释放堆内存。

当然,上面办法是可行的,但是,如果存在多个可能异常的函数,那么我们都使用TRAPD捕获异常的话,所造成的系统开销就会非常之大,清除栈就是为了解决这个问题而存在的,并且它很好的解决了这样类型的一系列问题。

二、使用清除栈

类CleanupStack定义在头文件e32base.h中,可以通过这个类提供的静态成员函数来访问清除栈。
清除栈的原理:
在调用可能发生异常退出的代码之前,非异常退出安全的对象应该被置于清除栈上,这可以保证在异常退出发生时,这些对象可以被正确的销毁。当发生异常退出时,清除栈能够将所有已经置于其上的对象回收。


下面就是一个使用清除栈的小例子:

void SafeFunctionL()

{

       CClanger* clanger = new(ELeave) CClanger;

       CleanupStack::PushL(clanger);

       clanger->InitializeL();

       clanger->DoSomethingElseL();

       CleanupStack::Pop(clanger);

       delete clanger;

}

实际上这个函数中的最后两条语句
CleanupStack::Pop(clanger);

delete clanger;


可以使用CleanupStack::PopAndDestroy(clanger);这一条语句来替代,它们是等价的。

如果在调用clanger->InitializeL();或clanger->DoSomethingElseL();的时候异常退出了,clanger对象就会被清除栈销毁。

comments:只有当你的调用可能(只要有可能)会导致异常退出,你就必须把它弄到清除栈上去,否则的话(也就是你能相当确认后续的操作不会导致异常发生),完全没有必要,而且如果你这样做的话也是浪费系统资源,清除栈上放一个指针虽然只有4个字节,但也是肉啊!

顺序问题:

对象必须以严格的顺序压入和弹出清除栈,一组Pop()操作必须与PushL()调用的顺序相反。可能会发生调用Pop()或PopAndDestroy()弹出对象而没有命名这些对象的情况,但最好还是要对弹出对象进行命名。

举例:

void ContrivedExampleL()

{

       CSiamese* sealPoint = NewL(ESeal);

       CleanupStack::PushL(sealPoint);

       CSiamese* chocolatePoint = NewL(EChocolate);

       CleanupStack::PushL(chocolatePoint);

       CSiamese* violetPoint = NewL(EViolet);

       CleanupStack::PushL(violetPoint);

       CSiamese* bluePoint = NewL(EBlue);

       CleanupStack::PushL(bluePoint);

     

       sealPoint->CatchMouseL();//入清除栈语句放在可能异常退出的代码之前

     
       CleanupStack::PopAndDestroy(bluePoint);

       CleanupStack::PopAndDestroy(violetPoint);

       CleanupStack::PopAndDestroy(chocolatetPoint);

       CleanupStack::PopAndDestroy(sealPoint);


}


可以看到出栈的顺序和入栈的顺序正好是相反的,不过在这里显得复杂了一点,上面的四个出清除栈语句可以使用 CleanupStack::PopAndDestroy(4);或 CleanupStack::PopAndDestroy(4,sealPoint);进行代替效果基本是一样的。


comments:清除栈,清除栈,也是一个栈啊,栈的操作就是先进后出,所以,最后push到栈上的要先pop出来,先push的最后出来,很简单吧!



创建在堆上的对象由谁销毁?

对于一个对象,清除永远不能超过一次。如果清除栈上有一个指向对象的指针,而后来又保存到其它地方了,譬如成了另一个对象的成员变量,而这个对象在异常退出后仍可以被访问,那么就应该从清除栈上弹出这个指针。如果在清除栈上保留了这个指针,那么清除栈会销毁它所指的对象,但是保存了该指针的对象也会试图通过析构函数销毁这个指针所指的对象。
因此,对象应该只被清除栈或另一个对象所引用,而不能同时被两者引用。类似的,永远不要将类成员变量压入清除栈。

小例子:

void TransferOwnershipExampleL()

{

       CClanger* clanger = new( ELeave ) CClanger();

       CleanupStack::PushL(clanger);//压入清除栈

       iMemberObject->TakeOwnershipL(clanger);//类成员iMemberObject获得了对象所有权
       CleanupStack::Pop(clanger);//这里就必须从清除栈中弹出对象指针

                          //调用完异常代码后将对象指针从清除栈中弹出


}

comments:
永远不要将类成员变量压入清除栈,会导致double-deletion

命名问题:
如果有对象被压入清除栈,并直至函数返回时还保留在清除栈上,则该函数应该以”C”作为后缀。这就告诉函数调用者,如果函数正常返回,清除栈上仍然有多余的对象。

CSiamese* CSiamese::NewLC(TpointColor aPointColor)

{

       CSiamese* me = new( ELeave ) CSiamese( aPointColor );

       CleanupStack::PushL( me );

       me->ConstructL();

       return me;

}

上面的函数实际是用在对象的二阶段构造中,其中,压入清除栈后并没有弹出,因此命名时必须要用”C”结尾。

三、对非CBase派生类使用清除栈


看一下CleanupStack::PushL()的三种重载形式:

(1)CleanupStack::PushL(CBase* aPtr)

(2)CleanupStack::PushL(TAny*)

(3)CleanupStack::PushL(TCleanupItem)



第一种形式:用在CBase的派生类对象上,当popanddestroy时,会调用该派生类对象的析构函数,跟C++一样,先调用派生层次最深类的析构函数,然后沿着派生层次顺次向上调用析构函数,最后调用CBase的空析构函数。


第二种形式:如果在定义一个C类时,忘记了从CBase派生,那么就会很危险(千万不要这么做,如果你这么做了,出什么事情,你自己要负全责),因为在调用PushL时,实际上调用的是CleanupStack::PushL(TAny*)这个方法,这样在pop时就不会调用这个类的析构函数,仅仅是清除它所指向的堆内存而已。实际上该方法被用来将没有析构函数的、基于堆的对象的指针压入清除栈(比如T类对象或结构体)。


第三种形式:接收一个TcleanupItem类型对象作为参数,这个函数可以使我们将其他类型的对象或那些具有定制的清除例程的对象压入清除栈。TCleanupItem对象封装了一个指向要保存在清除栈上对象的指针和一个指向提供相应对象清除操作的函数指针。


另外,Symbian OS还提供的三个用于清除的工具函数–
CleanupReleasePushL(), CleanupDeletePushL(), CleanupClosePushL(),分别对应是Release()、Delete()、Close(),都会生成一个TCleanupItem对象让我们能够自己定义清除的过程。并结合下面三个入栈方法。当然,这里入栈的对象引用,可以是创建在堆上的任意对象,比如C类对象,R类对象,T类对象,M类对象等等均可。
(1)       CleanupReleasePushL( T& aRef注意参数不是T*) //


异常退出的处理或PopAndDestroy()调用将对T类对象调用Release()。

举例:

class MExtraTerrestrial

{

public:

          virtual void CommunicateL() = 0;

          …..//出于整洁,略去接口其他代码

          virtual void Release() = 0;

}


class CClanger : public CBase , MExtraTerrestrial

{

public:

          static MExtraTerrestrial* NewL();

          virtual void CommunicateL();

          virtual void Release();

private:

          CClanger();

          ~CClanger();

private:

          ……..

}

void TestMixinL()

{
MExtraTerrestrial* clanger = Clanger::NewL();

CleanupReleasePushL(*clanger);//参数不是指针,这点和普通PushL不同

……..//执行可能发生异常退出的代码

CleanupStack::PopAndDestroy(clanger);//这里是指向对象的指针

}

注意:入清除栈和出清除栈时的参数是不一样的。

(2)       CleanupDeletePushL( T& aRef)

通过使用CleanupDeletePushL()可以使异常退出处理或PopAndDestroy()调用对对象施以delete操作,进而调用对象的析构函数,并且相应的堆内存也会被释放。这就类似于使用接受CBase指针的CleanupStack::PushL()重载函数。当必须要将M派生类指针置于清除栈上时,该函数尤为有用。


(3)       CleanupClosePushL( T& aRef类对象内置了Close()方法,不用另外添加了。) //R

如果对象是通过CleanupClosePushL()压入清除栈的话,则异常退出处理或PopAndDestroy()调用将对对象施以close()操作。

void UseFilesystemL()

{
RFs theFs;

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

CleanupClosePushL(theFs);
……..//执行可能发生异常退出的代码
CleanupStack::PopAndDestroy(&theFs);

}


Symbian OS 提供的这三个工具模板函数,它们分别对应于Release()、Delete()、Close()这三个清除方法,这3个工具函数都会生成一个TCleanupItem类型的对象并将其压入清除栈中。

comments:其实是上面的三个函数会生成一个TCleanupItem对象,然后这个对象会自动调用相应的Close()或是其他的函数。


小结:
系统中每个分配了资源的可执行单元(或者线程)都有它自己的清理栈和最高级别的TRAP/TRAPD宏来做异常处理和一些退出后的善后工作。之所以引入清除栈,就是为了解决堆内存泄漏的问题,注意是堆内存,如果对象被创建在了栈上的话,这是不关清除栈的事的,因为栈上的对象所占空间由栈自动管理。

注意:对于C类对象而言,CleanupStack::Pop()方法仅仅是将C类对象指针从清除栈中弹出了而已,并没有调用这个C类对象的析构函数,若要析构,需要再加语句delete c,或者可以直接使用CleanupStack::PopAndDestroy()同时完成上面两个动作。

Symbian OS异常三步曲之三:两阶段构造(ConstructL()) zz

为了在堆上分配一个CExample对象内存空间,调用该类的构造函数,如果构造函数本身就异常退出了,那么分配给对象的内存和构造函数中已经分配的内存都将泄漏,因此,C++的构造函数绝对不能发生异常。如果,为了初始化一个对象,必须要编写发生异常的代码,例如内存分配或读取可能丢失的文件,损坏了的配置文件等,这时候,就需要使用两段构造了。

一、两段构造的格式:

编写一个类时,将构造代码分为两部分
1、 一个基本的不会发生异常退出的构造函数——C++
的默认构造函数

这个构造函数将被new操作符调用,它隐式的调用基类的构造函数,另外还可以调用那些不会发生异常退出的函数,也可以以默认值或传入构造函数的参数来初始化成员变量。

2、 一个类方法(通常称为ConstructL())
只要通过new操作符分配并构造的对象被压入了清除栈,该方法就可以单独调用了,它将继续完成对象的构造过程,这里面可以安全的执行那些可能发生异常退出的操作。即使发生了异常,由于之前已经将对象指针入栈,清除栈可以调用类的析构函数来释放所有已经成功分配的资源,并回收分配给对象本身的内存。

实例:

class CExample : public CBase

{

       public:

              static CExample* NewL();//静态函数

              static CExample* NewLC();//静态函数

              ~CExample();//析构函数必须public,否则delete不能调用

       private:

              CExample();//绝对不能异常

              ConstructL();//将构造时所有可能异常的代码放在这里

}


为了安全和使用上的方便,往往在类的定义里面添加两个静态的函数NewL()和NewLC(),作用主要是将两段构造关联到一起。

CExample* CExample::NewLC()

{

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

       CleanupStack::PushL(me);

       me->ConstrutL();

       return me;

}

CExample* CExample::NewL()

{

       CExample me = CExample::NewLC();

       CleanupStack::Pop(me);

       return me;

}

当然,NewL()和NewLC()函数是可以接受参数的,并可以通过参数来初始化对象。这些参数可以传递给简单构造函数,也可以传递给ContructL(),或者同时传递给两者。


二、使用须知:

如果你的类派生自某基类,而该基类也实现了ConstructL(),那么就要保证当对象被构造时,如果需要,基类的ConstructL()也会被调用(C++能够保证基类第一段简单构造函数会被构造函数自动调用),因此,需要我们自己在ConstructL()中显式的(通过域操作符)调用所有基类的ConstructL(),这样才能保证在初始化派生类对象之前,基类子对象都已经被完全构造了。

SymbianOS异常三步曲之一:异常退出(leave)zz

Symbian的异常处理有别于标准C++的异常处理机制,主要原因是最初在设计Symbian的异常处理机制时,C++还没有引入异常处理,但是从Symbian OS 9.1开始,Symbian开始支持标准C++的try—catch异常处理机制,不过考虑到系统开销以及兼容性的因素,我们提倡使用Symbian特有的异常处理机制即异常退出。
一、异常退出函数
        当调用异常退出函数或显式调用系统函数时可能会发生异常退出。如果一旦异常退出发生,就会抛出一个异常,并同时产生一个错误码,这个错误码会沿着调用栈传递,直到被最近的一个异常捕获模块所捕获。
注意:一旦发生异常退出,异常退出点后的代码不再执行,而是转入最近的捕获模块,捕获完毕接着执行捕获模块后的代码。这样做,并不会中止程序流,这和侦测程序错误的断言assert是不同的(Symbian中常用的断言宏有_ASSERT_ALWAYS_ASSERT_DEBUG),断言会中止程序继续执行。
       异常退出函数是执行了不能保证一定成功的操作(例如在低内存容量下分配内存)的函数。
关于异常退出函数的返回值:除非需要将函数中所分配资源的指针或引用作为返回值(比如:NewL()函数通常是返回一个对象的指针),否则异常退出函数应该是没有返回值的。关键是我们将一个函数做成一个异常退出函数好还是做成一个返回错误代码的普通函数合适。后面我们会做详细分析。
举几个异常退出函数声明的例子:
void InitializeL( ); // 无返回值的异常退出函数
static CTestClass* NewL( ); //返回指针的静态异常退出函数
RClangerHandle& CloneHandleL( );
      
从上面3个例子可以看到:异常退出函数的名字都是以“L”结尾的,这是必须的,如果不用L表明异常退出函数的话,别人在调用你的函数时,可能由于没有捕获异常,导致潜在的内存泄漏发生。
       函数在下列3种情况下可能发生异常退出:
(1)       调用了可能异常退出的代码,并且在调用代码的周围没有使用异常捕获模块。
(2)       调用了一个会产生异常退出的系统函数,如User::Leave( )或User::LeaveIfError( )
(3)       使用了以ELeave为参数的new操作符重载形式。
下面看一下这几个系统异常退出函数的理解:
(1)User::LeaveIfError( ):会测试传入其中的一个整数参数值,如果该值小于零,譬如在e32std.h中定义的某个KErrXXX错误常量,则产生一个异常退出。这个函数可用来将返回标准Symbian OS错误码的无异常退出函数转化成一个以对应值作为异常退出码的异常退出函数。
(2)User::Leave( )不作任何参数值的检查,而只是简单的异常退出,并以传入的整数值为异常退出码。
(3)User::LeaveNoMemory( ):只是简单的异常退出,但异常退出码被硬编码成KerrNoMemory,它的效果等同于调用User::Leave(KErrNoMemory)。
(4)User::LeaveIfNull( ):接受一个指针作为参数,如果该指针为NULL,则以KerrNoMemory为异常退出码发生异常退出。
后缀“L”在编译时不被检查,所以由于我们忘记添加“L”或给原来的无异常退出函数添加了异常退出代码等原因,为此可以使用Symbian OS为我们提供的工具LeaveScan
二、使用new(ELeave)进行基于堆的内存分配
使用new(ELeave)来为对象在堆上分配内存,当内存不足时将会发生异常退出。因此我们可以直接使用其返回的指针,而无需做进一步的测试来确定内存是否分配成功。实质是,new(ELeave)中已经对指针是否为NULL进行了if判断。
       举例:
       CClanger* InitializeClangerL( )
{
              CClanger* clanger = new(ELeave)CClanger();
              if(clanger == NULL)//这条语句多余,可以省略
{
                     CleanupStack::PushL(clanger);
                     clanger->InitializeL();
                     CleanupStack::Pop(clanger);
}
return clanger;
}
上面的例子中对clanger进行了是否分配内存成功的测试,实际是多余的,完全可以省略。
三、构造函数和析构函数
这两个函数是绝对不允许发生异常退出的。因为如果构造时发生异常,那么可能会因为缺乏足够的资源而无法创建或初始化对象,但对象空间已分配,导致内存泄漏;如果析构时发生异常,将导致对象的不完全析构,这就可能造成资源的泄漏。
       解决上面问题的办法其实很简单:将构造函数中可能异常的代码提取出来,放在一个单独的异常退出函数ConstructL()中,利用二阶段完成对象的构造。也可将析构函数中可能异常的代码提取出来,放在CommitL()FreeResource()中,在析构之前调用它,给调用者一个机会来处理可能发生的问题。
四、使用异常退出函数
例1:
void FunctionMayLeaveL()
{
       CTestClass* ironChicken = CTestClass::NewL();
       ironChicken->FunctionDoesNotLeave();
       delete ironChicken;
}
例2:
void UnsafeFunctionL()
{
       CTestClass* test = CTestClass::NewL();
       test->FunctionMayLeaveL();
       delete test;
}
比较上面两个例子:
(1) 两个函数都是异常退出函数,都可能异常退出,并且ironChickentest都是局部变量。
(2) 我们在调用两个函数中的类CTestClass的NewL方法产生对象指针以后,无需对指针进行是否NULL的判断。因为构建成功的话,指针绝对不会为NULL;而构建失败的话,NewL方法将异常退出。
(3) 由于采用二阶段构造,即使NewL方法构建对象失败,仍然能够保证ironChicken和test的堆内存安全释放。
(4) 但是例1中,ironChicken调用不会异常的函数,因此delete ironChicken可以使ironChicken的堆内存安全释放;而例2中,test调用可能异常的函数,一旦异常发生,那么delete test语句将会执行不到,从而导致test所指的堆内存不能得到释放,从而导致内存泄漏,这是需要特别注意的。
针对于例2,再看下面这个例3:
void CTestClass::SaftFunctionL()
{
       iMember = CclangerClass::NewL();//iMember是类成员
       FunctionMayLeaveL();
}
在这个例子中,我们首先构造了iMember,然后同样是调用一个异常退出函数FunctionMayLeaveL(),但是这里即使发生异常,iMember所指的堆内存仍然可以安全释放,因为在这里iMember是类CTestClass的成员,它的堆内存释放是由析构函数来完成的,而不是在FunctionMayLeaveL()语句后执行delete释放的,所以即使这里发生异常,只要能够调用析构函数,也不会发生堆内存泄漏。
五、用TRAP和TRAPD捕获异常退出
1、使用格式
Symbian OS提供了TRAP和TRAPD这两个宏来捕获异常。它们使用区别,仅仅在于使用TRAP之前,需要事先声明保存异常错误码的变量,而TRAPD不需要,可以直接使用。并且在捕获异常之后往往会有一个if的异常结果判断语句。
       TRAPD(result,MayLeaveL());
       If ( KerrNone != result )
{
              //错误处理
}
TInt result;
TRAP( result,MayLeaveL( ) );
If(KerrNone != result)
{
       //错误处理
}
       2、宏TRAPD的嵌套和同名变量的使用
      
       (1)同名
TRAPD(result , MayLeaveL( ));
       If ( KErrNOne == result )
       {
              TRAPD ( result , MayAlsoLeaveL( ));
}
User::LeaveIfError(result);
这个例子的本意是只要这里的两个异常函数MayLeaveL()和MayAlsoLeave()有一个发生异常,都会导致User::LeaveIfError( )发生。但事实上,由于错误码重名,导致User::LeaveIfError()仅能看到第一个result,而第二个result实际上被第一个result屏蔽了,因此为了看到第二个异常的结果,我们应该使用另一个异常错误代码名字,可以在if语句外面定义一个新的错误码变量,然后使用TRAP捕获第二个异常。
实际上,User::LeaveIfError()永远不能捕获到第二个异常的错误码,因为一旦MayLeaveL()发生,就不会进入if语句,因此,第二个异常函数也就根本运行不到了。
(2)嵌套
为了系统开销上面的考虑,我们尽量不要使用TRAPD嵌套,尽量考虑使用其他的办法来替代。
譬如我们可以将如下代码:
TInt MyNonLeavingFunction( )
{
       TRAPD( result , FunctionMayLeaveL( ) );
       if(KErrNone == result )
              TRAPD(result , AnotherFunctionWhichMayLeaveL( ) );
       if(KErrNone == result )
       TRAPD(result , PotenialLeaveL( ) );
return result;
}
为了避免嵌套,将可能产生异常的函数集中到一个函数中,将上面代码改为:
MyNonLeavingFunction()
{
       TRAPD(result , MyLeavingFunctionL( ) );
       return result;
}
void MyLeavingFunctionL()
{
       FunctionMayLeaveL();
       AnotherFunctionWhichMayLeaveL();
       PotentialLeaveL();
}
3、不应该写这样的函数:将错误码作为返回值返回,同时又有可能发生异常退出。
TInt OpenFileObjectL(CFileObject* aFileObject)
{
       Rfile file;
       TInt error = file.open(…..);
       If(KErrNone == error)
{
              CleanupClosePushL(file);
              aFileObject = CFileObject::NewL(file);
              CleanupStack::Pop(&file);
}
return error;
}
上面这个函数就写的不太好,这个函数的返回值是错误码,但是在使用这个函数时还可能产生异常退出,从而传出异常退出码。
我们一般这样使用上面这个函数:
void ClientFunctionL()
{
       CFileObject* fileObject = NULL;
       TInt errorCode = OpenFileObjectL(fileObject );
       If ( KErrNone != errorCode)
{
              User::Leave(errorCode);
}
}
上面的使用仅检测了OpenFileObjectL方法的错误码,而没有捕获异常,不妥。
或者这样:
TInt ClientFunction()
{
       CFileObject* fileObject = NULL;
       TInt errorCode;
       TRAPD(r,errorCode = OpenFileObjectL() );
If(KErrNone!=r)
              return r;
       if(KErrNone!=errorCode)
              return errorCode;
}
这里既分析了错误码,又捕获了异常,但总感觉放在一起判断时,有点不伦不类,也不妥。
那么应该怎么办呢?
有两种办法:
(1)       用异常错误码取代错误码,也就是统一为异常退出。
即将原来的TInt OpenFileObjectL(CFileObject* aFileObject)方法做如下修改:
                      CFileObject* LeavingExampleL() //CFileObject对象指针通过函数返回值获得,//而不是之前那样通过引用形参获得。
{
   RFile file;
  User::LeaveIfError(file.Open(…..));//错误码转化为异常抛出
   return CFileObject::NewL(file);
}
然后可以这样使用:
void ClientFunctionL()
{
   CFileObject* fileObject = NULL;
   TRAPD(r,fileObject = LeavingExample());
   switch(r)
{
case(KErrNoMemory)
…….//释放一些内存
break;
……….
default:
User::Leave(err);
    break;
}
}

这样进行处理时就比较统一了,仅对异常进行switch进行讨论即可,风格比较统一了。
(2)       用错误码取代异常错误码,也就是统一为错误码。
TInt Example(CFileObject*& aFileObject)//函数返回值为错误码
{
   RFile file;
   TInt error = file.open(…..);
   If(error == KErrNone)
{
  TRAP(error,aFileObject = CFileObject::NewL(file));//将异常转换成错误码
}
return error;
}
这样进行使用:
CFileObject* fileObject = NULL;
User::LeaveIfError(Example(fileObject));

                   这样进行处理时,风格就比较统一了,仅需处理错误代码即可。

                   相比较而言,将错误代码转化为异常退出进行统一处理的系统开销较小,也更简单,推荐使用这个办法。


写的过于罗嗦,早个SDK上的例子看看别人是怎么做的,然后follow就OK了,说了很多如果弄不清的话很容易把人弄晕了,呵呵~~

C语言的未定义行为

int a=5,b;

b=++a*–a;

b的值是多少呢。。

这是C语言的未定义行为,C标准没有对其进行定义,编译器可以随意进行计算

随后测试vc6的种种行为

//总结:

//a=5;

//b=++a*++a;    49

//b=a++*++a;    36

//b=++a*a++;    36

//b=a++*a++;    25

3.2 使用我的编译器,下面的代码int i=7; printf(“%dn”, i++ *

i++); 返回49?不管按什么顺序计算, 难道不该打印出56吗?

尽管后缀自加和后缀自减操作符++ 和– 在输出其旧值之后才会执行运算,

但这里的“之后”常常被误解。没有任何保证确保自增或自减会在输出变量原值之

后和对表达式的其它部分进行计算之前立即进行。也不能保证变量的更新会在表

达式“完成” (按照ANSI C 的术语, 在下一个“序列点” 之前, 参见问题3.7) 之前

的某个时刻进行。本例中, 编译器选择使用变量的旧值相乘以后再对二者进行自

增运算。

包含多个不确定的副作用的代码的行为总是被认为未定义。(简单而言, “多

个不确定副作用” 是指在同一个表达式中使用导致同一对象修改两次或修改以后

又被引用的自增, 自减和赋值操作符的任何组合。这是一个粗略的定义; 严格的定

义参见问题3.7, “未定义” 的含义参见问题11.32。) 甚至都不要试图探究这些东

西在你的编译器中是如何实现的(这与许多C 教科书上的弱智练习正好相反); 正

如K&R 明智地指出, “如果你不知道它们在不同的机器上如何实现, 这样的无知可

能恰恰会有助于保护你。”

对于上面这段话,还是有点理解不透。。。。水平不到。以后应该就可以理解了吧。。