• 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);


    }

     

     

  • 相关阅读:
    k8s 重要概念
    k8s 核心功能
    5 秒创建 k8s 集群
    学习 Kubernetes 的 Why 和 How
    Yeelink初步体验
    为Qemu aarch32添加BeautifulSoup4模块
    实现Qemu aarch32虚拟开发板ping www.baidu.com
    利用/proc/pid/pagemap将虚拟地址转换为物理地址
    加快Qemu Aarch32虚拟开发板的启动速度
    为Qemu aarch32开发板添加sd卡
  • 原文地址:https://www.cnblogs.com/xianqingzh/p/1695385.html
Copyright © 2020-2023  润新知