Symbian开发/描述符

维基教科书,自由的教学读本

简介[编辑]

描述符在Symbian操作系统中至关重要,除了进行字符串处理,还能存储二进制数据。许多函数的参数都是描述符,因此必须学会高效的使用描述符。

第二章将讲解内存相关概念;第三章将讲解不同描述符类的继承关系并具体分析不同的描述符;第四章介绍描述符相关API;第五章介绍传统C字符串方法和描述符方法的对应关系。

变量声明和内存[编辑]

这章讲解字符串声明操作在内存中的表现,也适用于理解其他类型变量的声明。对于资源有限的设备,内存管理尤其重要。所以要想写出高效的程序,就应该理解这些Symbian OS编程的基础概念。

2.1节将介绍逻辑内存区的概念;2.2节复习C字符串的声明。

变量和内存[编辑]

声明一个变量后,它的值保存在内存中。根据声明的不同,保存的方式也不同。

可执行代码[编辑]

源程序通过编译和链接变成的二进制形式,叫做可执行代码。当声明静态、常数变量时,数据存储为可执行代码的一部分。

这种变量在程序代码中被初始化,而且因为是常数,运行时不能改变。可执行代码放在ROM或者RAM的只读区域上,使其不会被其他代码修改。因此,静态常量数据和字面量都嵌入在可执行代码中,可以作为可执行代码的一部分传递。

在整个程序运行期间,这种变量都是有效的,程序员完全不用担心内存管理的问题。

全局变量[编辑]

当变量声明为静态但不是常数时,就是全局变量。在Symbian操作系统中,只有.exe可执行文件可以拥有全局变量,如果为.dll文件声明了全局变量,则无法为ARM平台编译。

一旦.exe文件作为进程开始运行,就会拥有一块内存区域专门用于存放其全局变量数据。因为软件设计中应该尽量避免使用全局变量,而且大量程序是.dll形式的,本文就不详细解释全局变量的用法了。

[编辑]

Symbian操作系统中的每个线程都有自己的栈。本地变量放在栈上,调用函数时的参数也都放在栈上。由于栈大小有限(特别是对Symbian操作系统),栈上只适合放少量数据。所以函数调用时,应该传递指针或者引用,避免在栈上分配对象的副本。

栈上变量的生命周期是固定的,当一个代码块结束时,在块中声明的本地变量就会被自动删除。如果该变量是一个类,在删除该对象前还会先调用它的析构函数。

[编辑]

Symbian操作系统中的每个线程都有自己的堆。通过C++关键字new动态分配的对象都放在堆中。new操作返回一个指针,指向分配的对象,或者如果堆上没有足够的空间的话返回NULL。

堆中的对象不想要的时候必须通过delete关键字释放掉。如果对象有析构函数,内存释放前会先调用析构函数。把对象留在堆中不删除叫做内存泄漏。在运行中,内存泄漏会逐渐耗尽剩余内存,导致程序无法继续,而且还会影响其他的进程,因为它们得不到需要的内存资源。最糟糕的情况下,必须重启机器或杀掉内存泄漏的进程。

一般而言堆比栈大一些。所有大个的对象都应该放在堆中而非栈上。只要牢记留下分配的对象的指针,不用的时候删除掉,因为不像栈,堆是不会自动清理的。

Symbian操作系统提供了类CleanupStack,其方法可以用来保管堆对象的指针。当堆对象不再需要时,指针从清除栈中弹出,指向的对象被删除掉。

C字符串[编辑]

本节讲述C字符串如何存储在内存中。

下面的静态常数变量存储在程序可执行代码中,共6个字节:H、e、l、l、o和结尾0。

static const char helloPgmBinary[] = "Hello";

使用时以helloPgmBinary作为数据的指针。由于是常数,数据不可更改。

要想将同样的字符串声明在栈上,可以这样:

{
  //在栈上分配一个大小为6的区域
  char helloStack[sizeof(helloPgmBinary)];
  strcpy(helloStack, helloPgmBinary);
} //程序块结束,本地变量helloStack从栈上删除

要想在堆中声明同样的字符串,可以这样:

{
   //在堆中分配大小为6的区域
   char* helloHeap = (char*)malloc(sizeof(helloPgmBinary));
   strcpy(helloHeap, helloPgmBinary);
   free(helloHeap);
}

malloc的结果是void指针,转换为字符指针并命名为helloHeap。程序块结束时,helloHeap指针本身将从栈中删除,如果没有释放堆中helloHeap对应的内存,这6个字节就永远找不到,也无法释放了。

描述符[编辑]

C字符串有很多问题,例如以字符数组为参数的函数无法得知数据大小,因为数组是作为一个指针传递的。要得到字符串长度,需要在字符串末尾放置一个NULL字符。NULL结尾字符串一般容易出问题而且效率较低。

因此在Symbian操作系统中,用描述符替代了C字符串。除了字符数据,它还存储字符串的长度。描述符还可以用于对缓冲区的处理。

通过宏_LIT,下面的代码将字面量字符串"hello\0"存储在可执行代码中:

_LIT(KHelloPgmBinary, "hello");

在Unicode环境中,上面的宏展开为:

const static TLitC<6> KHelloPgmBinary={5,L"hello"};

可以看到,这里声明了一个静态常数对象KHelloPgmBinary,并用字符串的长度和字符数据初始化。由于是在Unicode环境中,因此字符数组加前缀L,声明为Unicode。编译器会据此创建16位的字符而非8位的。

要想将变量声明为本地的,放在栈上,可以这样:

TBuf<5> helloStack(KHelloPgmBinary);

TBuf是模板类,加上模板参数5后成为可以存储至多5个字符的类。构造函数的参数本应是TDesC,而非变量KHelloPgmBinary的类型TLitC<6>,但是TLitC包含一个空的类型转换运算符,可将其自动转化为TDesC类型,因此在这里可以作为参数传递。构造函数将字面量的长度和字符赋值给helloStack实例。

要想在堆中为该变量分配一个缓冲区描述符,有若干种方法,例如:

HBufC* helloHeap = HBufC::NewLC(KHelloPgmBinary().Length());
*helloHeap = KHelloPgmBinary;
CleanupStack::PopAndDestroy();

上面的代码中,首先用运算符()将字面量KHelloPgmBinary转换为TDesC类型。TDesC有Length()方法,可以得到字符串中的字符数。HBufC提供factory方法NewLC,在堆中分配一个缓冲区描述符,大小通过参数指定,返回分配的对象的指针。

许多类都提供工厂方法(factory method)用来分配和构造对象,而不使用new关键字。这些方法一般叫做New或者NewLC。后缀L表示如果分配或者构造失败,方法会Leave(Leave是Symbian中抛出异常的方法)。后缀LC除此之外还将该对象推入清除栈顶。

第二行的复制操作将KHelloPgmBinary自动转换为TDesC类型,然后就可以赋值给HBufC变量,包括复制字符和设置字符串大小。

当不再需要堆中的对象时,需要将其删除。因为NewLC已经将对象推入清除栈,所以只需要弹出指针并删除对应对象,使用PopAndDestroy()方法就可以完成该操作。在这之后,块结束时就可以无后顾之忧的将helloHeap指针从栈上删除。

Symbian描述符详述[编辑]

上章已经简单的介绍了如何用描述符存储字符串。描述符包含很多处理字符串的方法。然而描述符,特别是8位描述符还能用来存储二进制数据。因此接下来的讨论将会更一般,而不会局限在字符串处理中。

描述符类型和继承关系[编辑]

第二章介绍了三种描述符声明,可以将字符串数据存储在不同的内存区域上。其实除此之外,不同描述符还包含其他不同的特征:

  • 是缓冲区还是指针?
  • 可不可以修改?
  • 数据存储在栈、堆、还是程序可执行代码中?
  • 字符是16位的还是8位的?

Symbian操作系统中有9种描述符类,每个类又分为非Unicode环境中的8位类型和Unicode环境中的16位类型,例如TDesC8和TDesC16。然而,也存在与环境无关的类。例如TDesC,根据编译环境的不同,它会映射为8或16位的类型。除非确切知道字符大小(如文件中的字符),应该首选这种环境无关的类。

环境无关类的继承关系如下图所示,包括4个虚的基类和5个可实例化的继承类:

  <<abstract>> TDesC     <-----------     <<abstract>> TDes
  iLength          ^                      iMaxLength      ^
  iType            |                      MaxLength()     |
  Ptr()            |                      ^               |
  Length()         |                      |               |
  ^                |                      |               |
  |                |                      |               |
  |                |                      |               |
  |                |                      |               |
TPtrC    <<abstract>> TBufCBase          TPtr    <<abstract>> TBufBase
iPtr        ^              ^             iPtr             ^
            |              |                              |
            |              |                              |
            |              |                              |
         TBufC<n>        HBufC                         TBuf<n>
         iBuf:TText[n]   iBuf:TText[n]                 iBuf:TText[n]

根据上图所示,TDesC是所有类的基类。所有的描述符都可以通过Length()方法获得数据数组的长度,也可以通过Ptr()方法获得指向首元素的指针。

下面介绍不同描述符的特性。

可修改 vs 常量[编辑]

类名的后缀C表示该描述符是常量。这样的描述符不能修改其内容,而非常量的描述符,如TPtr和TBuf<>,就有方法可以修改内容。不过,常量描述符TBufC<>和HBufC都包含一个Des()方法,返回一个TPtr对象,就可以修改内容数据了。TPtrC是唯一没有任何办法修改内容的描述符。

缓冲区 vs 指针[编辑]

如上图所示,Symian操作系统中有三个缓冲区描述符和两个指针描述符。指针描述符用来指向内存中的一块数据,而缓冲区描述符自身结构中就包含数据数组。

TBufC<>、HBufC和TBuf<>对象中都包含了数据,也知道数据的长度。这些缓冲区类最多可以存储的数据长度在构造时指定,具体的长度就在零和这一上限之间变化,因此缓冲区类很适合用于处理数据。TPtr和缓冲区描述符功能一样,不过对象本身不包含所指向的数据,而是指向存储在另外一块内存区域中的数据。

TPtrC指向一个固定大小的数组。它不提供任何可以改变内容的方法,因此适合指向常量数组或者可能更改内容的缓冲区。因此,有些方法,例如TDesC::Mid()会返回这种描述符用来表示原始数据中的一部分。