原文:Symbian中的descriptor
作者:ilovevc
好久沒有寫blog了,最近一直在symbian上開發,對symbian中的字串進行了一點總結,格式沒有編排,而且比較混亂,實在懶得整理了。
TDesC本身只包含兩個成員變數,type和length。
type用於避免虛擬函數,length表示對象的長度。這樣也就決定了symbian的descriptor是一個封閉的系統,即開發人員無法再從系統提供的Descriptor class通過繼承以擴充其功能。
主要的成員函數有:
TInt Length() const;
const TUint8 *Ptr() const; 返回一個不可修改的指標。
TDes由於涉及到修改,因此增加了一個MaxLength,返回能夠容納的最大長度。
麻煩的是,它並沒有提供一個函數返回一個可寫的指標。WPtr是保護的。
因此,TBuf由於從它間接派生,也有一個iMaxLength成員。
而TBufC,HBufC就沒有。
這兩個類都是抽象類別。但是可惜的是,symbian原始碼中沒有將編譯器自動產生的拷貝建構函式去掉。
因此下面的代碼和合法的。
extern TPtr ptr;
TDesC des = ptr;
TPtrC從TDesC派生,多了一個類似const char ×的成員變數指標
同樣,TPtr從TDes派生,也多了一個char *成員變數Ptr指標
注意;TPtrC和TPtr本身並不“擁有”記憶體,而只是指向一個已經分配好的記憶體。而且TPtrC/TPtr的解構函式理所當然也不free它
指向的記憶體了。
另外,當調用TPtr/TDes提供的函數修改記憶體,當然實際的記憶體就修改了。
另外,甚至TPtr本身的這個指標成員變數iPtr可能並不一定就指向實際記憶體的首地址,唯一保證得到首地址的方法就是調用
TDesC中的Ptr()函數。例如如果你通過調用TBufC::Des()得到的TPtr對象實際它的iPtr實際指向前4個位元組位置(在Windows
Simulator中)。
如果TPtr是通過TBufC/HBufC的Des()函數得來,那麼調用TDes裡面的函數影響到iLength的時候,TBufC/HBufC中的iLength也改
變了。
如果通過建構函式得到TPtr,因為TPtr指向的記憶體可以是任何可寫的資料,因此它隻影響自己的長度。
這樣,就實際上有兩種類型的TPtr,一種當長度改變的時候,需要同時設定TBufC/HBufC的長度,另外一種不要。
估計是通過TDesC裡面的iType來區分。
TBufC間接從TDesC派生,有一個類似const char[]的成員變數。基本上沒有什麼存在的必要。
畢竟我們在棧中分配記憶體,基本上就是為了“可寫”的使用它,而TDesC沒有提供任何“寫”函數。
幸虧它還提供了Des()函數獲得一個可寫的TPtr/TDes對象.不過還是麻煩了一些。
TBuf間接從TDes派生,
kO,中Z]垠(網業56
有一個類似char[]的成員變數。
但是它的父類TDes也沒有方法返回一個可寫的指標。有一個WPtr()還是Protected的。sigh!
HBufC從TDesC派生,搞不清為什麼沒有一個HBuf類。
提供了一個Des()函數返回一個可寫的TPtr對象。
另外,由於Descriptor既可以表示字元,又可以表示binary資料。因此預設記憶體配置的時候並不保留一個額外的''''/0''''位置。
例如TBuf<3> buf; 那麼這個buf的大小就確實只是3。
TLitC就是特別為string準備的,因此使用sizeof操作符,char[]中已經包含了''''/0''''字元。但是cast回的TDesC的長度可能仍然
是strlen
_LIT(KStr, "hello");
TDesC desc = KStr;
int len = desc.Length(); //check the length
HBufC8 * hbuf1 = HBufC8::NewLC(1);
HBufC8 * hbuf2 = HBufC8::NewLC(4);
HBufC8 * hbuf3 = HBufC8::NewLC(8);
TDes des1 = hBuf1.Des();
des1 = hBuf2.Des();
des2 = hBuf3.Des();
CleanupStack::PopAndDestroy(3); //HBufC8
TDes / TDesC 是抽象,%6Gf.KM2Ks;2TPtr/TPtrC是輔助,TBuf/HBufC/TBufC這些才是具體資料儲存的地方。
在VC中檢查symbian的記憶體流失:
F5 直到程式Panic,然後在output window中找到類似Thread panic ALLOC: f90b380,找到對應的記憶體位址。
重啟emulator,檢查該地址對應的內容。可以在退出的時候檢查,也可以在啟動的時候設定“Data"斷點。
Symbian中的Descripor不要互相賦值。例如
TPtr ptr = buf.Ptr(0);
ptr = buf.Ptr(1); //ptr可能並沒有被修改,$$vC8$p網r*導致不期望的行為。
這個地方需要極度留心,所有可寫的Descriptor,例如TBufC,TBuf,TPtr,HBufc,symbian都重載了operator=()函數,實際
的語義並不是你可能想要的類似於指派陳述式,而是將右值的內容拷貝到左值Descriptor所指的記憶體。
例如:
TBuf8<5> buf1(_L8("hello"));
TBufC8<5> buf2(_L8("world"));
TPtr8 ptr( (TUint8*)buf1.Ptr(), 5, 5);
ptr = buf2.Des();
最後一句,可能原來的意思是使ptr指向buf2,但是實際上變為了buffer之間的Copy指派陳述式,即將buf2的內容拷貝到ptr所指
的記憶體中,運行以後buf1和buf2的內容都為world。
同時,TPtr的拷貝建構函式和等於asignment操作符的含義不同。
extern TPtr b;
TPtr a = b; //調用a的拷貝建構函式,a,b指向同一塊buffer
TPtr a; //編譯錯誤,沒有預設建構函式。必須先分配記憶體
a = b; //將b中的內容copy至a所指的記憶體中。
symbian中的字串基本上可以以兩種眼光來看待:
const和非const,即唯讀和可寫的。
unicode和非unicode,即8bit和16bit字元。
因此假設一個類TPtr8,則後面加C,TPtrC8表示const版本,8變為16表示unicode版本。
TPtr被在unicode定義的情況下被typedef成TPtr16,否則為TPtr8。
symbian的字串總是使用unicode。
symbian使用descriptor來表示一段資料,該資料既可以在記憶體中,也可能在唯讀ROM中,既可以表示字串,也可以表示二
進位資料,因此不依賴''''/0''''字元,甚至根本就沒有''''/0''''字元。
所有descriptor的基類是TDesC,該類實際上為一個抽象類別,在symbian的標頭檔中,該對象的所有建構函式都為protected。因
此你無法建立TDesC,也永遠不會(應該說你不應該)出現如下的語句: TDesc des。
但是symbian可能疏忽了,也比較程式容易出錯的是,它沒有屏蔽C++自動產生的拷貝建構函式。
因此下面的語句沒有任何編譯警告:
extern TPtrC ptr; //TPtrC是TDesC的衍生類別
TDesC des = ptr; //調用預設的拷貝建構函式,
中yVv件HKJ)]
造成slice切割
foo(des); //調用一個函數,需要const TDesC&參數,由於資料已被切割,結果無法預料。
它主要用於函數參數傳遞,作為函數參數時,不是const TDesC& des,就是const TDesC× des,它表示一段唯讀資料,有數
據成員iLength表示該段記憶體的長度。
以下是TDesC的資料成員定義
private:
unsigned int iLength:28;
unsigned int iType:4;
由此可知,Descriptor能代表的最大記憶體長度為0x0FFFFFFF,剩下的4bit,iType表示衍生類別的類型。
其中,TDesC還有一個重要的成員函數:
public:
const TUint *Ptr() const; 返回一個指向該段資料的指標。通常來說,這個應該定義成一個純虛擬函數,由衍生類別實現。
symbian為了避免虛函數帶來的開銷,使用iType來表示衍生類別的具體類型。不難想像,TDesC根據iType就可以得到具體類的數
據頭地址。
TDesC所有的成員函數都是const修飾,即任何函數都不會修改this指標。
TDesC實際上是一個typedef,對應unicode的版本,它定義為TDesC16,非unicode則為TDesC8,如果表示位元據時,可以顯
示使用TDesC8。
TDes表示一段可寫的資料區域,前面說了,所有的descriptor都從TDesC派生,TDes也不例外。增加了一個資料成員,
protected:
TInt iMaxLength;
表示該段地區的最大有效長度,由於TDes不是const了,因此可寫。iMaxLength可以防止越界記憶體訪問。
該類也是一個抽象類別,即無法直接構造。
一個基本的原則就是,唯讀Descriptor總是直接或間接地從TDesC派生,而不會從TDes派生。
可寫的Descriptor總是直接或間接地從TDes派生,當然由於TDes的基類是TDesC,因此也從TDesC派生。
TPtrC表示一段唯讀記憶體,從TDesC派生。由一個指向唯讀資料的指標和該段資料的長度初始化。
增加了一個成員變數: protected: const TUint *iPtr;
例如:
const char * psz = "hello";
TPtrC8 ptr(psz, 5); //ptr指向psz,長度為5。
TPtrC8 ptr(psz + 1, 2); //ptr指向 ‘e'''' 到 ''''l'''',長度為二。
TPtr表示一段可寫的記憶體,從TDes派生。由一個指向可寫資料的指標和該段資料的長度初始化。
增加了一個成員變數:protected: TUint *iPtr;
例子:
char psz[] = "hello";
TPtr8 ptr(psz, 5); //ptr指向psz,長度為5。
TPtr8 ptr(psz + 1, 2); //ptr指向 ‘e'''' 到 ''''l'''',長度為二。
TPtr並沒有提供額外的函數來操縱、修改資料,都由基類TDes提供。
例如;
ptr.LowerCase(); //小寫轉換
TPtr和TPtrC並不真正“擁有”資料,它們只是“指向”該段資料,因此解構函式自然也不free掉這段記憶體。
上面的 char psz[] = "hello";在symbian中,一般使用TBuf來定義。
TBuf是一個模板類,唯一的模板參數是擁有資料的大小。簡化後,定義如下:
template <TInt S> class TBuf8 : public TDes
{
protected:
TUint8 iBuf[S];
};
因此如果我們在棧中定義上面的“hello"字串,[oGxn_V$-JV-供S"x_M就可以如下:
//由於TBuf沒有提供適合const char×的合適的建構函式,sEQ國Cc3sq提因此我們需要將const char ×
//強制轉換成const unsigned char *。sigh!
TBuf8<5> buf((const TUInt8*) "hello");
然後我們利用TDes提供的函數可以:
buf[0] = ''''H''''; // "Hello"
buf.LowerCase()等 // "hello"
還有一個對應的TBufC,提供唯讀版本,從TDesC派生,照我看,基本上是照顧TBuf有個匹配的const class,用處不大。
nokia series 2.0 sdk裡面的幾十個examples,沒有一個使用它。我也不想用。:)
就好像你使用下面的語句:
const char hello[];
你在棧中分配一塊記憶體,類型為const char。除非你一定義就初始化它,否則使用const修飾只是自找麻煩。
最後一個就是在堆中分配記憶體的class,例如在c中我們一般如下:
char * psz = (char *)malloc(20);
free(psz);
在symbian中,提供了一個HBufC類,注意最後帶C的總是直接/間接從TDesC派生,而和TDes沒有任何關係。
那麼啟不是只能使用基類TDesC中提供的唯讀成員函數?
下面是HBufC的定義:
class HBufC8 : public TDesC8
{
public:
IMPORT_C static HBufC8 *New(TInt aMaxLength);
IMPORT_C TPtr8 Des();
private:
TText8 iBuf[1];
};
注意iBuf[1]這種寫法在c中經常用來定義可變大小結構。
前面提到HBufC8間接從TDesC8派生,
ANVEg.YiZ育G&+4國h
因此只能使用TDesC提供的“唯讀”成員函數,幸虧它還提供了一個Des()函數返回一個TPtr8
指標,由於TPtr由TDes繼承,因此我們通過TPtr就可以修改它的內容了。sigh!
期待HBuf class?對不起,symbian沒有提供。到此為止了。
例子:
HBufC8 * pbuf = HBufC8::New(100); //等價於 const char * psz = (const char *)malloc(100);
delete pbuf; //等價於 free(psz);
注意上面的const char *,sigh!分配記憶體後返回的指標用const修飾,那我分配一塊唯讀記憶體幹什嗎?
因此如果要幹點正事,必須調用HBufC的Des()函數得到一個可寫的TPtr對象。
HBufC8 * pbuf = HBufC8::New(100); //等價於 const char * psz = (const char *)malloc(100);
TPtr8 ptr = pbuf->Des(); //類似於 char * p = (char *) psz; 好了,rPA9A(64網"md^A_6業總算可寫了。
ptr[0] = ''''H''''; // *p = ''''H''''; 調用TDes中的函數
ptr.SetLength(1); // p[1] = ''''/0''''; 調用TDes中的函數
delete pbuf; //等價於 free(psz);
不知道基於什麼理由symbian定義了一個HBufC,而不是HBuf。估計是為了省一個TDes中的iMaxLength的資料成員?
不過從”const“的HBufC 調用Des() 函數轉換到“非const”的TPtr,這個額外的iMaxLength,也不知道symbian是如何?的?
^_^,原來是調用static TInt User::AllocLen(const TAny* aCell)函數。