原创 Symbian OS开发基础

2008-3-31 21:16 4646 8 8 分类: 软件与OS
这个星期开始接手Symbian OS的开发任务,以前从没有做过SYmbian的东西,所以花了些时间看文档,现在的事情是应用层的,因此OS的内核基本没有涉及到.S60平台是Nokia绝大多数手机的平台,我不认为symbian比linux强,不过在手机领域它的市场确实是最大的.symbian的应用层开发与熟悉的windows下的开发,标准的C++以及linux下的开发都不同,差别较大,很不习惯.下面把这几天看到的一些基础的东西贴在下面,以后有空再写个详细的,这个大多数是从网上看来的,不是我自己写的.
基本数据类型及命名规范

Basic Types


 在Symbian中,很多C++基本类型都被重新定义了,最好使用Symbian的,理由如下:



所有Symbian API都是用的Symbianc重定义的
将来Symbian OS由32位转为64位时,支持性更好
这本身就是Symbian C++ Coding Standards所要求的

 Integers


    typedef signed int TInt;  C++中的signed int,32位,基本用法类似。


    typedef unsigned int TUint;  一般用于计数器(Counter)或者标记(Flags)。


其他Int类型:TInt64, TInt32, TInt16,TInt8; 同时有一份TUint的版本。


Text


 text类型在Symbian编程中基本不用,而一般采用描述符(descriptor)。TText默认是16位的。


Boolean 


    typedef int TBool; 有两个枚举值:ETrue和EFalse。TBool变量最好不要直接和ETure和EFalse比较。如下:


TBool flag = ETrue;
if (flag)  // if (!flag)

    flag = EFalse; 
}


 Floating Point


    对浮点数的支持视处理器而定,如果没有FPU,效率非常低,所以最好是不要用浮点数。 如果一定要用,尽量转化为整数操作。


typedef float TReal32;  typedef double TReal64; typedef double TReal;


TAny


    typedef void TAny;


TAny一般只用作指针,其他情况下用void比较好。


TAny* MyFunction();     void MyOtherFn();


TAny* 在很多Symbian API中都用到了,如:


static TUint8* Copy( TAny* aTrg, const TAny* aSrc, TInt aLength);


Enumerations


enum TState {EOff, Eon, EInit};


Enumeration类型应该以T开头,而枚举值应该以E开头。


TState  state = GetState();
if (state == EOn)
{
   //Do something here


Coding Conventions


    T类:只包含值,而不包含指针以及外部的资源,在栈上分配空间。


TVersion osVersion = User::Version();


    C类:所有需要分配内存的类都必须从CBase继承并且以C开头。


class CExample : public CBase
{
private:
   CDesCArrayFlat* iArray;
}    


CExample* example = new (ELeave) CExample;


    R类:包含指向某个资源的handler。


RTimer timer;
timer.CreateLocal();


    M类:定义一个接口,一般只包含纯虚函数,不包含成员数据,减少类之间的依赖,用来接受回调消息。


class MEikStatusPaneObserver
{
   public:
        virtual void HandleStatusPaneSizeChange() = 0;
}


任何实现MEikStatusPaneObserver接口的类都必须实现HandleStatusPaneSizeChange()函数。


Variable Naming Conventions



    成员变量以“i”开头
    参数以“a”开头
    动态变量随便,以小写字母开头
    常量以“K”开头
    尽量不要使用全局变量,不能使用全局静态变量。

Functions



    函数以大写字母开头,如AddFileNameL();
    以D结尾表示deletion of an object
    以L结尾表示函数可能leave
    以C结尾表示一个item被放到cleanup stack

Casting


    Casting用于在类(classes)和类型(types)之间作转化,Symbian中仍然可以使用C中语法。


    dynamic_cast:不支持,Symbian中没有RTTI。


    static_cast:把一个基类转化为一个继承类。 


TInt intValue = 0xff;
TUint8 byteValue = static_cast<TUint8>(intValue);


    reinterpret_cast:把一个指针类型转化为另外一个指针类型,如integer转化为point类型或者相反。


TUint32 fourBytes = 0;
TUint8* bytePtr = reinterpret_cast<TUint8*> (&fourBytes);
bytePtr++;
*bytePtr = 0xFF; 


    const_cast:移除一个类的const属性

内存管理
规则:
Always delete objects your class owns, from the class destructor. 
Don't delete objects that you don't own (that is, those that you merely use).
Don't allocate twice (this will cause a memory leak). 
Don't delete twice (this will corrupt the heap). 
When you delete outside the destructor, immediately set the pointer to zero. 
When you are reallocating, you must use the sequence 'delete, set pointer to zero,
allocate', just in case the allocate fails. 
Use new(ELeave) rather than plain new. 
Use L on the end of the name of any function that might leave. 
Use traps where you need to ¨C for instance, when a function can't leave and must
handle errors privately ¨C and not where you don't.
Push an object to the cleanup stack if (1) that object is otherwise only referred to by an
automatic pointer, and (2) you are going to call a function that might leave during that
object's lifetime. 
Never push a member variable to the cleanup stack ¨C use theiMember naming
convention.
Give all heap-based classes a name beginning with C, and derive them from CBase.
Trust CBase to zero initialize all data, including all pointers. Exploit CBase's virtual
destructor for cleanup purposes. 
Never leave from a C++ constructor. 
Put construction functions that might leave into a second-phase constructor such as
ConstructL(). 
Optionally, use NewL() and NewLC() to wrap up allocation and construction.


   Symbian OS本身就是为内存和资源受限的设备开发的,应用程序运行过程中很可能碰到内存用光,或者硬件资源不可用的情况。而这种exceptions是通过修改程序无法解决的,所以遵守以下几条:



尽量不要使用不必要的RAM
尽早释放资源,如文件server等
当你每次申请内存时,都须准备处理out-of-memory错误
当 out-of-memory错误发生时,返回到一个stable的状态,并释放所有期间申请到的资源

 Stack and Heap


    Stack:默认大小8kb,自动删除,如 TInt i = 0;


    Heap :至少0.5Mb,由程序员手动删除,如 CMyObj* obj = new (ELeave) CMyObj;


Leaves


    首先介绍Conventional C++ Memory Management,在Symbian看来,这是非常低效率的。



NULL Pointer Checking  if ((myObj = new CMyObj( ) ) == NULL) { //Error Handling }

ANSI C++ Exeption Handling   try { //throw an Exception } catch (int e) { //Error Handling }

    在Symbian中推荐采用Leave,如果内存或者资源不能分配到,这个代码就会Leave,沿着Call Stack,直到操作系统或者在某个函数中被Handle掉。


    所有可能Leave的函数最好以L结尾,保证该函数的用户知道这个函数可能Leave。


    Leave的例子:



动态内存分配: return new (ELeave) TUint8[1000];
产生一个Leave:User::Leave(KErrNotFound);
内存不足时Leave:User::LeaveNoMemory();
NULL的时候Leave:User::LeaveIfNull(aNotify);
当发生错误时Leave:RFs fs; TInt err = fs.Connect(); User::LeaveIfError(err);

    处理Leave:


    操作系统有默认的处理Leave的方式:



在程序启动过程中:直接关闭应用程序。
应用程序启动后:显示一个错误消息。 

    开发者可以通过trap装置来处理Leave。TRAP(_r, _s)和TRAPD(_r, _s),其中:



_r:是一个TInt类型的leave code,默认值为TErrNone。

_s:一系列可能Leave的C++ Statements。

        TRAPD(err, DoFunctionL());
        if (err != KErrNone)
        { //Error Handling }
        else
        { //Everything is well }


  The Cleanup Stack


    cleanup stack用于存储在leave发生后需要deallocating的局部变量(指针)。即:当一个函数leave了,所有在cleanup stack上的对象会被全部删除掉。


    Cleanup Stack的使用方法:


CleanupStack::PushL(ptr) :当发生leave时所有内存都会被释放
CleanupClosePushL(handle):当发生leave时这个句柄(handler)会被关闭


CleanupStack::Pop(pointer):第一个元素出栈
CleanupStack::PopAndDestroy(pointer):第一个元素出栈并释放内存


    如果一个函数可能leave,检查一下两种情况:



如果leave了,是否所有在堆(heap)上的元素都在cleanup stack中了
如果没有leave,你是否自己恰当地将他cleanup了

 CMyClass* CMyClass::NewL(TInt aBufSize)
{
   CMyClass* self = new (ELeave) CMyClass;
   CleanupStack::PushL(self);
   self->ConstructL(aBufSize);
   CleanupStack::Pop(self);
   return self;
}


    如果某个函数会在cleanup stack上留下一个对象,那么他必须以C结尾。


Two Phase Construction


    C++构造函数一定不能leave。所有内存和资源的分配应该在第二阶段构造函数ConstructL( )中完成。


编码指南,所有用户定义的C类必须:



定义NewL和NewLC函数为public static
定义ConstructL和C++ Constructor为private

Best Practise


     Construction的规则:



默认的C++构造函数中不能含有可能leave的代码
可能发生leave的函数必须在ConstructL中被调用
如果基类也有ConstructL,必须首先调用,不要忘了explicit scoping

    Destruction的规则:



C类必须在析构函数中删除它自己所包含的对象
在删除一个对象后,把它的指针设为NULL
不要删除不是本类所拥有的对象
在reallocation前首先删除对象,并且将其指针设为NULL

    Further Discussion:



Preserve Stack Memory:每个进程只有8K,以引用的方式传递参数,大的对象放在堆上
Preallocation vs last moment allocation:一般的原则是只在使用前分配资源并且在使用后马上释放。但是                     preallocation的好处是节约处理时间,并且在没有内存的情况下照常运行(资源已经分配到了)
where to put trap harness:最基本的情况是依靠GUI应用程序的框架。根据应用的不同,可以自定义粒度。
Error Code Returns vs. leaving functions:在执行某个处理前检测是否会出现问题,如下代码:

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


Memory Leaks


    如果你的程序有内存泄露,在模拟器上关闭时会crash。尽早发现并解决你的内存泄露,因为你可以追查到你可能导致内存泄露的代码改动。如果实在找不到,可用下面方法:


    Heap Balance Checking:



_UHEAP_MARK
_UHEAP_MARKEND

    用上述这两个宏放在你要检查的代码的开头和结尾,如果发生panic,则说明这段代码中发生了内存泄露。可以嵌套使用。 


Panics 


    Panic是一个未经处理的exception,暗示着一个无法解决的错误。


一般程序有以下三类错误:



程序错误:如引用一个超过数组范围的元素
环境错误:内存、磁盘空间不够,或缺少其他资源等
用户错误:输入错误数据

    可以使用trap和cleanup stack技术来解决环境和用户错误,但是对于第一类的程序错误,我们无法恢复,最好是使用User::Panic()函数,它带有两个参数,第一个是string,第二个是Tint。


描述符

Introduction


    描述符(Descriptors)封装了字符串和二进制数据,用于替代C中的以NULL结尾的字符串。它的长度和数据都封装在了描述符中,Symbian API中用的都是描述符。如:


    TPtrC ptr (KHelloWorld);    CEikonEnv::Static()->InfoMsg(ptr);


 Main Types of Descriptors


    主要可以分为以下几类,其中带C的是不可修改的。 



  • Abstract:(TDes、TDesC),其他描述符的基类,不能实例化,一般用作函数的参数。
  • Literal:(TLitC,_LIT()),用于存储literal string,一般使用后者。
  • Buffer:(TBuf,TBufC),数据存储于栈上,大小在编译时确定。
  • Heap:(HBufC),数据存储于堆上,大小在运行时确定。
  • Pointer:(TPtr,TPtrC),引用存储于类之外的数据

Descriptor Modification


    描述符可以是可修改的和不可修改的,通常带C的都是不可修改的,不可修改的是可修改的基类。   



  • Moidfiable:提供了访问和修改数据的API,如TBuf
  • Non-Modifiable:数据只可以被访问,不可修改。但是通过Des()函数可返回一个可修改的指针

Descriptor Width


    在描述符类后加上8或者16影响了存储在描述符中的数据的宽度,默认是16位的,处理二进制或ASCII时采用8位。



  • 8位:(TDesC8),用于二进制数据或者ASCII字符串
  • 16位:(TDesC16),默认,Unicode


    Abstract Descriptors


        除Literal外的所有描述符的基类,提供了基本的接口和基础功能。他们本身不能实例化,一般用作函数参数。


    TDesC:提供了比较、复制、搜索、提取部分字符串的函数。


    TInt TDesCUtil::SumLengths(const TDesc& aDesC1, const TDesc& aDesC2)
    {
       return aDesC1.Length() + aDesC2.Length();


    TDes:继承自TDesC,添加了许多用于修改数据的函数。其最大长度是描述符被创建时确定的。


    TInt TDesCUtil::AppendL(TDesc& aTarget, const TDesc& aDesC)
    {
       TInt sumLen = aTarget.Length() + aDesC.Length();
       if (aTarget.MaxLength() < sumLen)
       {
          User::Leave(KErrOverflow);
       }
       aTarget.AppendL(aDesC);


     Literal Descriptors


        提供了一种将字符串放在只读存储空间中的机制(实际存放在程序的数据区,而不是真的在ROM中)。一般不采用TLitC而直接采用_LIT()宏。    _LIT(KHelloWorld, "Hello World!");


        通过()操作符可以得到 const TDesC&。 TInt length = KHelloWorld().Length();


        在函数参数为const TDesC&可以直接使用KHelloWorld。iLabel->SetTextL(KHelloWorld);


     Buffer Descriptors


        将数据作为本身的一部分存储在stack上,他们的最大长度是在编译时确定的。  


    TBuf<16> helloWorld = KHelloWorld;
    TInt len = KHelloWorld().Length();
    helloWorld[len-1]='?'; 


    TBufC的用法如下:


    _LIT(KHelloWorld, "Hello World");
    const TInt maxBuf = 32;
    TBufC<maxBuf> buf;
    TInt currentLen = buf.Length(); // == 0
    buf = KHelloWorld;
    currentLen = buf.Length(); // == 11
    TText ch = buf[2]; // == 'l'


     TBuf的用法如下:


    const TInt bufLen = 6;
    TUInt8 objType = 1;
    TUInt8 objId = 1;
    TUInt8 xCoord = 128;
    TUInt8 yCoord = 192;
    ....
    TBuf8<bufLen> buf;
    buf.Append(objType);
    buf.Append(objId);
    ...
    //we can now do something with the buffer such as writting it to a binary file or send via socket.


    Pointer Descriptor


        用于引用存储在其他地方的数据,如:


    const unsigned char KBuffer[ ] = {0x00, 0x33, 0x66, 0x99, 0xbb, 0xff};
    TPtrC8 bufferPtr( KBuffer, sizeof(KBuffer));
    iSocket.Write(bufferPtr, iStatus);


    TPtr的用法:


     _LIT(KHelloWorld, "Hello World");
    const TInt maxBuf = 32;
    TBufC<maxBuf> buf;
    buf = KHelloWorld;
    TPtr ptr = buf.Des();
    ptr[7] = 'a';  ptr[8] = 'l';  ptr[9] = 'e';  ptr[10] = 's';  
    CEikonEnv::Static()->InfoMsg(ptr); // "Hello Wales"


     Heap Descriptors


         动态在堆(heap)上分配,通过HBufC的API,数据可以被set和reset,但是不能被修改。如:


    HBufC* heapBuf = HBufC::NewL(KHelloWorld().Length());
    *heapBuf = KHelloWorld();
    delete heapBuf; 


    HBufC通常在以下几种情况下使用: 



    •  在运行时从资源文件中加载字符串
    •  从用户界面中接收用户输入的字符串
    • 从应用程序引擎中接收字符串,如contacts database中的名字 

         对HBufC中的内容进行修改:


    _LIT(KHello, "Hello!");
    _LIT(KWorld, "World!"); 
    HBufC* heapBuf = HBufC::NewL(KHello().Length());
    *heapBuf = KHello;    //buf holds "Hello!"


    heapBuf = heapBuf->ReAllocL(KHello().Length() + KWorld().Length());
    CleanupStack::PushL(heapBuf);


    TPtr ptr (heapBuf->Des());  //DON'T use TPtr ptr = heapBuf->Des(); this will set maxlen to 6 but not 12...
    ptr[KHello().Length() - 1] = ' ';
    ptr += KWorld;
    iTopLabel -> SetTextL(ptr);
    CleanupStack::PopAndDestroy();
    DrawNow();

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
我要评论
0
8
关闭 站长推荐上一条 /3 下一条