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