Symbian 逐步深入(二)

一、本章节建立在您已经掌握了最基本的命名规则的基础上进行.

Symbian的对象:

              创建

       通常使用C++new来进行分配,很少使用User::Alloc()之类的用户库函数来分配。为什么呢?因为这样如果出现由于内存不足时,将引发错误。

       一个内存分配的例子

Class CMyClass:public CBase

{

      public:
       CMyClass();

       ~CMyClass(); //析构函数

void Foo();     //自定义的函数

private:

Tint iNumber;

TBuf<32> iBuffer;

}

//下面使用堆分配

CMyClass* myCls=new<ELeave> CMyClass;

//接下来我们不需要去判断分配成功与否注意与C++的区别

 myCls->Foo(); //一旦new的操作有返回,其实已经可以确保它已经成功了

delete myCls;

        删除

   由于SymbianC++设计中,对象的使用者有可能不是拥有者,所以我们对对象进行删除可能导致两次删除的问题。如何避免这一点:由于C++中的Delete操作并不是把指针置0.如果从类的析构函数外部删除对象成员,一定要确保将成员指针设置为NULL。因为对一个指针本身置0的对象来说。2次删除是没有关系的。

 

Symbian中的线程一旦启动,则其栈就不能再增长,一旦栈溢出,则会导致栈异常终止。所以对于比较大的资源(任何比文件名长的字符串)最好是放到堆中。然后创建一个指针指向它!--指针放在堆栈上完全可行。Symbian中只有T类可以放到栈中。函数退出不需要任何处理。

 

类的分类

Symbian的类有2种分类,C类和T

C类由CBase类派生而来,在对上分配。尤其要注意的是:我们一般用一个类的成员变量或类的自动变量等去拥有C类的对象,则如果C类在一个可能异常退出的函数中,我们也要做好保存好这个指针,在Symbian中我们用清理栈来达到这个目的。

 

T类暂时不介绍

R类是一个句柄,具备了T类的特点很少用指针,一般用引用;也具备C类的特点,负责它指向的资源的分配和释放。我们使用Close()来进行释放。

 

Symbian的错误处理

 

PushL()是一个异常退出函数,但是你可以放心,所有放入清除栈的指针即使在异常退出的时候也能得到清除,这是因为PushL()必须有空内存,只有当内存容量非常低的情况下,继续压入才会产生异常。假设这种情况发生了,那么已经压入清除栈的数据依旧会得到删除

 

PushL()可能导致的错误:PushL()只能在TRAP宏内运行,其他在TRAP宏外运行使用CleanStack::PushL()都将导致错误。错误代号:E32User-CBase66

 

TARP和异常退出是一种内部实现。如果捕捉到其他种类的异常,TARP就可能会产生严重错误。

 

Symbian两阶段构造

 

为了避免出现这样一种问题:程序在构造函数初始化了部分信息的时候,出现异常而退出。那些已经分配了内存的、已经得到初始化的数据信息就遗留在内存中得不到释放。Symbian的解决方案是采用两阶段构造。

       正规构造函数什么也不做,需要完成某些初始化的动作放在一个叫ConstructL()的函数中。同时为了避免大部分会误用ConstructL().—我们将它声明为private,然后定义一个public static类型函数去执行,这类函数通常是NewL(),他们能够在拥有一个此类的实例之前被调用个。

       看代码(新建一个GUI工程的时候自动生成的代码)

CtestDDDocument* CtestDDDocument::NewL(CEikApplication& aApp)

    {

    CtestDDDocument* self = NewLC(aApp);

    CleanupStack::Pop(self);

    return self;

    }

 

CtestDDDocument* CtestDDDocument::NewLC(CEikApplication& aApp)

    {

    CtestDDDocument* self = new (ELeave) CtestDDDocument(aApp);

 

    CleanupStack::PushL(self);

    self->ConstructL();

    return self;

    }

 

void CtestDDDocument::ConstructL()

    {

    // No implementation required

    }

 

这里我们介绍下 FunLC()这种LC结尾的函数,说明对象已经被推入到清除栈,并保留了其中的信息。代码如下:

Eg:

    CExample* CExample::NewLC()

    {

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

      CleanupStack::PushL(me);

      me->ConstructL();

      return (me);

    }

 

两构造函数可以确保当对象生成的时候,就已经完全初始化了,如果会出现异常,只会出现在 new(ELeave)中或ConstructL()中。

 

瘦模板

 

瘦模板的存在的原因在于:

 

<class T> class TmyCls

{

}

TmyCls <Tint> TintMyCls;

TmyCls <TUint> TUintMyCls;

//类似这种情况,会生成2个类模板的副本,虽然不会带来效率的降低,但是编译后代码的尺寸则增大。对于做手机开发是很致命的!这就是瘦模板存在的意义!

我们可以考虑用这种方式实现:

 

 

Class TmyCls

{

    protected:

       IMPORT_C TAny* Fun1(TInt aParam);

}

然后

<class T> class TSubMyCls:private TmyCls

{

    Inline T& DoFun1(TInt aParam)

    {

       Return *((T*)Fun1(aParam));//返回的是地址

    }

}

 

虽然在TSubMyCls中也会产生两个副本,但是由于函数的实现是在TmyCls当中。所以其内存分配较小,不会造成浪费。

    另外,为什么要私有继承呢?A类私有继承B类,则B类中的protected方法、public方法在A类中都将变成private。是因为实际中TAny* 不是类型安全的,而如果通过公有继承,那么子类是可以转化成父类(编译器允许)。因为公有继承本身就有是一个的概念。但是如果通过私有继承,只是表达这样一种关系:...来实现”,这里的意思是:TSubMyCls可以用TMyCls的某些方法来实现。TSubMyCls只关系父类的实现的方法,和对象层次没有任何关系。

    私有继承一旦使用,等于再告诉所有人:父类的使用是不安全的,它只是用来实现其他的类。所以  TmyCls cls; //这是错误的,因为一般构造函数是protected类型。

    看如下的代码:(C++代码)

    /////Stack

class GenerateStack

{

protected:

    GenerateStack();

    ~GenerateStack();

protected:

    void Push(void* psObj);

    void* Pop();

    bool Empty() const;

};

//注意 这里使用 GenerateStack gs; //error

//int 类型的Stack

class IntStack:private GenerateStack

{

    public:

       inline void Push(int* intObj)

       {

       GenerateStack::push(intObj);

       }

       inline int* Pop()

       {

        return static_cast<int*>(GenerateStack::Pop());

       }

       inline char* Pop()

       {

        return GenerateStack::Empty();

       }

};

//如果是char 类型的Stack

class CharStack::private GenerateStack

{

    public:

    inline void Push(char* chObj)

    {

    GenerateStack::push(chObj);
    }

    inline char* Pop()

    {

    return static_cast<char*>(GeneratteStack::Pop());

    }

    inline bool Empty() const

    {

       return GenerateStack::Empty();

    }

};

//上面的方式就可以解决:内存分配多的问题

//唉,手工写那么多类很麻烦,怎么办?用模板

<class T> class TStack::private GenerateStack

{

    public:

    inline void Push(T* TObj)

    {

       GenerateStack::push(TObj);
    }

    inline T* Pop()

    {

    return static_cast<T*>(GenerateStatic::Pop()); q q

    }

    inline bool Empty()

    {

       return GenerateStatic::Empty();

    }

};

 

//通过使用模板,我们很容易通过

TStack <int> intStack; //便提供了一个int类型的Stack;

TStack <char> charStack; //便提供了一个char类型的Stack;

----参考文章:http://www.kuqin.com/effectivec2e/ch10f.htm

 

 

ps:描述符(字符串)

TDesC

TDes

----------最基础的类,能申请到系统分配给描述符的目前内存数据的最大长度

TPtrC

TPtr

    -------可以操作描述符数据存储之外的数据信息,如:只读存储器、堆内存、栈内存等

 

TBufC

TBuf

---------基于栈缓存、TBufC<int> 用来容纳不可变的缓存描述符,它是一个瘦模板类,分配的大小是在编译期间确定。

 

HBufC

    --------编译期间大小不能确定或者数据较大,对应于C就是 malloc进行分配的函数。

 

RBuf

    ------直接从TDes继承而来,使用方便,它其实也是一个瘦类型。

    RBuf使用的例子:

    RBuf myRBuf;

LIT(KHelloWorld,”helloWorld”);

myRBuf.CreateL(KHelloWorld);

使用例子2

HBufC* mHBuf= HBufC::NewL(20);

RBuf myRBuf;

myRBuf.Assign(mHBuf); //取得控制权

myRBuf要重新指定的时候,就需要调用Close()方法。

RBuf重新分配内存的时候,需要如下的步骤:

myRBuf.CleanUpPushL();//RBuf类推入清除栈, 清除工作在调用Close()(或者是调用CleanupStack::PopAndDestroy()时进行。

myRBuf.ReAllocL(newLength);

    CleanUpStack::Pop();

 

_LIT_L

其相当于 static const char[], 看如下的语句:

_LIT(KThis,”this”);//_LIT宏在二进制代码中构建一个TLitC16类型的对象。存储字符串,如”this”. _LIT_L宏在 e32def.h中进行定义。

 

描述符的相关操作API:

TDesC

Length()//== strlen()

Size()//==sizeof()

MaxLength()//申请的字符的最大长度

TPtr(C)::Set()TDes::operator=()//用来将指针指向其他的字符串数据

TBuf::Des()HBufC::Des()//返回一个指向缓存所容纳可变字符串指针。

 

数组

   Symbian的数组分为静态数组和动态数组。

静态数组:C/c++一样,多提供了很多额外的函数如 范围检查功能

动态数组:动态数组有如下两种实现方式:1.采用内存堆,每个分段都有一个独立的堆来保存/容纳数组元素;2.采用双向链表

建议使用RArray,这个唯一的不足是:它要求数组中的所有元素都需要以4字节对齐。

----------------------------------数组部分还有很多内容,在平时多揣摩

 

*小注意点:

    SymbianIDE Carbide C++可以制作出 exedll文件。如何区分他们呢。

    UID1-通过UID1,UID1被构建工具设置成KdynamicLibraryUid(0x10000079)而当为exe程序时,UID1则被设置成KExecutableImageUid(0x
1000007a)

      UID2—这个UID用来区分是共享库还是多态接口DLL. 对于共享库,UID2被设为KsharedLibraryUid(0x1000008d);但对于多态接口DLL它则根据不同的插件类型而取不同的值(比如,对于套接字服务器协议模块,UID2的值为0x1000004A).

UID3

UID3-这个UID值表明了文件的唯一性。任何两个可执行文件的UID3值都是不相同的,并且此值需要从Symbian申请。Symbian有一个中央数据库来分配UID3,以保证每个二进制文件都拥有不同的值。要想为您的产品代码申请UID3值,就必须在Symbian Signed(www.
symbiansigned.com)
注册。但是您也可以为了测试代码而在开发区间里(0xE1000000-
0Xefffffff)
自行分配一个值。

 

 

~~!!!

如果我们希望把数据传输到文本文件或其他外部设备,则需要外部化”,反之则为内部化”,有两个基类来实现这种外部化和内部化工作,RReadStreamRWriteStream。对应每一种介质:文件、描述符、内存缓存。则有不同的方法对(方法总是成对出现的)

1.  文件

    RFileReadStream

    RFileWriteStream

2.  描述符

    RDesReadStream

    RDesWriteStream

3.  缓存

    RBufReadStream

    RBufWriteStream

4.  内存

    RMemReadStream

    RMemWriteStream

 

另外,如果希望把创建的推入清除栈以保证其异常退出安全,则需要调用流的PushL().

    外部化对应的符号是:<<,需要重写ExternalizeL()

    内部化对应的符号是:>>,需要重写InternalizeL()

 

一个完整的流操作的例子:

    #include<s32mem.h>

    class CStreamSample:public CBase

    {

      public:

      void ExternalizeL(RReadStream& arStream) const;

      void InternalizeL(RWriteStream& awStream) cosnt;

      public:

      Tint iIntValue;

      TBuf<64>iBuffer;

    };

 

void CStreamSample::ExternalizeL(RReadStream& aStram) const

{

 aStream.WriteInt32L(iIntVal); //这个函数是什么意思?

 aStream<<iBuffer;

}

 

void CStreamSample::InternalizeL(RWriteStream& aStream) const

{

iIntVal=aStreamm.WriteInt32L();

aStream>>iBuffer;

}

void SampleFuncL(TAny* a,Tint aMaxLen)

{

//为一个描述符分配一个空间

 TBuf buf;

 buf.Create(64);

buf.CleanupClosePushL();

buf.Append(KSampleBuffer,1);

//

CStreamSample* sSample=newLC(1,buf);//这又是?

RMemWriteStream write;

write.PushL();

write.Open(a,aMaxLen);

write<<*sSample;

write.CommitL();

CleanupStack::PopAndDestory(&write);

 

buf=KSampelBuffer2;

CStreamSample* sSample2=newLC(2,buf);

RMemReadStream read;

read.PushL();

read.Open(a,aMaxLen);

read>>*sSample2;

read.CommitL();

CleanupStack::PopAndDestory(4); //….4??why is 4

}

 

Symbian的活动对象:

    活动对象与多线程

win32的程序为了实现执行多任务,采用多线程的方式;虽然Symbian也支持多线程,但是提出活动对象是因为:资源有线。多个活动对象上下文之间的切换较之于多线程上下文的切换,效率更高。

我们看一下这个例子:

    win32中,socket编程阻塞在CScoket::Receive()函数上。请求方只有在收到返回的网络字节后才能进一步进行程序处理。

    Symbian平台中,则为RSocket::Receive(),该函数是异步函数,所以不会在这个阶段阻塞。函数的声明:

    IMPORT_C void Recv(TDe8 &des,TUint aFlag,TRequestStatus &aStatus);

aStatus的初始值为ERequestPending,当事情处理完毕的时候,则变成 EActive。所以我们可以用如下的方法来辨别请求完成与否:

    TRequestStatus iStatus(ERequestPending);//初始化

    RSocket::Receive(aDes,aFlags,iStatus); //

    for(;;)

    {

    if(iStatus!=ERequestPending)

    break;

    }

   

    Symbian OS建议我们使用异步方法(上面的死循环代价太高了),事实上活动对象的意义就在于此,它构建了一个框架,在这个框架中已经有一个机制去辨别status是不是为ERequestPending了。

    活动对象的处理流程:系统中有一个活动调度器”,我们建立一个活动对象 a1,该对象进入如下流程:1.与系统中的异步函数绑定-à把活动对象注册到活动调度器中-à活动调度器会等待异步函数返回的完成消息。收到完成消息后,调度器会遍历所注册的活动对象,去寻找那些 status!=KRequestPending对应的活动对象,然后执行RunL方法,以事件的方式告知我们异步函数已经执行完成。

   

    从上述的流程中可以认识到:活动调度器是桥梁~

CActiveScheduler* scheduler=new(ELeave)CActiveScheduler();

CActiveScheduler.Install(scheduler);//这里把调度器指向类内部的一个静态指针,后面的代码可以方便的使用CActiveScheduler的一系列函数

    如:

IMPORT_C static void Add(CActive* aActive);//将活动对象加入活动调度器注册,以备使用

IMPORT_C static void Start();

IMPORT_C static void Stop();

上面已经说了ActiveScheduler,接下来说说活动对象CActive

我们使用一个类继承CActive,则子类会生成如下的东西:

public:

     TRequestStatus iStatus;//判断异步函数调用完成与否

private:

    TBool iActive; //用于证明对象是否已经申请了异步函数

如:

    RTime::After(iActive,100000);

    SetActive();//为什么要这样子呢?

    SetActive其实就是将 iActive=ETrue,用来标示活动对象已经申请了异步调用。所以一旦我们调用了异步调用,就需要有SetActive()函数。

    之后,我们要实现两个重要的函数:

    virtual void DoCancel()=0;

    virtual void RunL()=0;

RunL()—上面已经说过,当一个活动对象执行完成之后,status会变成EActive。这个状态的改变是由活动对象自动识别的,执行完成之后做的事情,我们需要在RunL()进行编码,首先要将iActive=EFalse,因为异步函数After传入的参数都是引用,所以可以改变。

 

DoCancle():基类CActive中的取消活动对象的方法Cancle(),之后通知活动对象调用DoCancle做善尾操作,如删除对象等,!!!终止对象是由Cancle()来完成!!!

 

AO,AS是很复杂的东西,需要你自己多去了解!

 

 

    遇到错误:KERN-EXE 3 错误原因是一个对象没有new,但是用它去指向一些东西

             CONF 14    资源id无法获取

              KERN-EXE X UI的时候发生错误,原因是

              E32USER-CBASE 40 调用基于CActive的析构函数时存在未完成的请求。

            case XX:

              {

              //error

               //break我写在这里

              }

              //success

              break;

 

 

_LIT(KDelayedHelloPanic, "Cdelayedhello");

    __ASSERT_ALWAYS(!IsActive(), User::Panic(KDelayedHelloPanic, 1));

 

 

Symbian线程

 

记住运行例子的时候,要与SDK安装盘符一致。不然会报 prc32找不到的错误

 

文件、流等。。

 

   Symbian系统与Dos的本质区别: 合格的文件名必不能超过256个字符。合格的文件肯定是TFileName的一个对象。

   大部分的Symbian平台中,所有的用户数据和系统文件都保存在c盘下,z盘是rom盘符。可拆离的的驱动盘符被映射成d盘或之后的那些盘符。S60中则是:d盘被映射成ram—非持久层。 

 

 

游离信号:

symbian不提倡多线程,原因在于symbian平台采用IPC服务端/客户端的模式对线程进行访问,为了达到这个目的,线程需要不断去读取,浪费时间和内存。

 

 

一个简单的例子:绘制

新建一个工程,然后修改draw函数就可以了。


void CdrawPicAppView::Draw(const TRect& /*aRect*/) const

{

// Get the standard graphics context

CWindowGc& gc = SystemGc();

//清空窗口上的内容  

gc.SetPenStyle(CGraphicsContext::ENullPen);

gc.SetBrushColor(KRgbGray);

gc.SetBrushStyle(CGraphicsContext::ESolidBrush);

gc.DrawRect(Rect());

gc.SetPenStyle(CGraphicsContext::ESolidPen);

gc.SetPenSize(TSize(3,3));

gc.SetPenColor(KRgbRed);

gc.DrawEllipse(Rect());

// Gets the control's extent

//TRect drawRect(Rect());


// Clears the screen

//gc.Clear(drawRect);


}

 

 

原文地址:https://www.cnblogs.com/xianqingzh/p/1695385.html