跳至內容

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()會返回這種描述符用來表示原始數據中的一部分。