Symbian Descriptor

symbian_foundation_logo

Symbian đưa ra Descriptor để thay thế cho String trong C/C++. Gọi là “Descriptor” vì chúng tự mô tả bản thân, mỗi đối tượng Descriptor đều cho biết thông tin về mình: độ dài của chuỗi mà nó nắm giữ, kiểu của ký tự, địa chỉ con trỏ trỏ tới ký tự đầu tiên, cách bố trí dữ liệu (data layout).

Descriptor không dùng ký tự NULL cho việc đánh dấu sự kết thúc chuỗi, nên chúng có thể chứa dữ liệu text hoặc nhị phân đều được. Có 2 loại Descriptor chính: Narrow và Wide.

  • Loại Narrow sử dụng 8 bit để biểu diễn một ký tự và do đó, con trỏ trỏ đến ký tự đầu tiên có kiểu TUint8*. Các lớp Descriptor thuộc loại này có tên gọi tận cùng bằng số 8 (ví dụ TDesC8). Ta thường dùng Narrow để chứa chuỗi ASCII hoặc nhị phân.
  • Loại Wide sử dụng 16 bit để biểu diễn một ký tự và do đó, con trỏ trỏ đến ký tự đầu tiên có kiểu TUint16*. Các lớp Descriptor thuộc loại này có tên gọi tận cùng bằng số 16 (ví dụ TBuf16) hoặc được typedef để không có số gì (ví dụ TBuf). Tuy nhiên nên viết tường minh số 16 để tránh nhầm lẫn. Ta thường dùng Wide để chứa chuỗi Unicode.

Mỗi lớp 16 bit đều có lớp 8 bit tương ứng và ngược lại, cho nên ta chỉ bàn về các lớp 16 bit. Cây kế thừa sau đây liệt kê các loại Descriptor và quan hệ giữa chúng:

Như bạn thấy, Symbian chia ra làm rất nhiều loại Descriptor, sự phân chia là để làm rõ những vấn đề sau:

  1. Descriptor có khả năng chỉnh sửa dữ liệu chuỗi của nó hay không?
  2. Descriptor chứa dữ liệu chuỗi ngay bên trong nó hay nó chỉ chứa tham chiếu đến dữ liệu chuỗi?
  3. Dữ liệu chuỗi được lưu trên Stack, Heap, hay ROM?
  4. Descriptor có khả năng tự tạo mới dữ liệu chuỗi không? Hay nó chỉ có thể trỏ đến dữ liệu chuỗi có sẵn?

Lớp TDesC

Đây là lớp cha của tất cả Descriptor. Tận cùng bằng C tức là dùng TDesC ta chỉ có thể đọc dữ liệu chuỗi của nó mà không thể chỉnh sửa được. TDesC chứa một biến thành viên iLength là độ dài chuỗi. Chú ý phân biệt Length (tổng số lượng ký tự) với Size (tổng số lượng byte).

TDesC không cần quan tâm đến dữ liệu chuỗi thực sự nằm ở đâu. Nó chỉ cần địa chỉ con trỏ trỏ đến ký tự đầu tiên bằng hàm thành viên: const TUint16* Ptr(); Thế là đủ để thực hiện các hàm như Match(), Find(), Compare(), … của TDesC.

Ptr() không phải là virtual function (và cũng chả có virtual function nào khác ở đây), các lớp con không hề cài đặt lại nó. Vậy làm sao TDesC biết được instance hiện tại thuộc về lớp con nào? Thực ra 4 bit đầu tiên của biến iLength dùng để nhận diện lớp con, 28 bit còn lại mới là lưu độ dài chuỗi. Số lớp con tạo instance được là 6, nên dùng 4 bit là quá đủ. Mỗi một trong 6 lớp con được đánh số thứ tự và có cách bố trí dữ liệu đặc thù, trong Ptr() sẽ dựa vào số thứ tự mà suy ra kiểu lớp con, từ đó suy ra tiếp địa chỉ con trỏ trỏ đến ký tự đầu tiên.

TDesC hay được dùng với tư cách là tham số chỉ đọc hoặc kết quả return chỉ đọc, trong các lời gọi hàm.

Lớp TPtrC

TPtrC có thể được instance, nhưng nó không có khả năng tự tạo ra dữ liệu chuỗi mà phải trỏ đến dữ liệu có sẵn, dữ liệu có sẵn có thể là một Descriptor khác hoặc là một con trỏ TUint16* bất kỳ.

Cách bố trí bên trong TPtrC: [iLength: 4byte] [con trỏ: 4byte]

TPtrC hay dùng trong tình huống có một nguồn dữ liệu cho bởi con trỏ TUint16* nào đó cùng với độ dài, chúng ta muốn truy vấn (không sửa) dữ liệu đó bằng cách dùng các hàm trong TDesC (TPtrC kế thừa TDesC).

Lớp TDes

Trong các lớp cho phép chỉnh sửa dữ liệu chuỗi thì TDes là lớp cao nhất, nó có chứa thêm biến iMaxLength hòng ngăn chặn sự tràn mảng, do dữ liệu chuỗi là thay đổi co giãn được.

Ngoài ra, TDes có thêm các hàm phục vụ việc chỉnh sửa dữ liệu chuỗi, ví dụ: Copy(), Append(), Replace(), …

TDes hay được dùng với tư cách là tham số hoặc kết quả return, trong các lời gọi hàm.

TBufC và TBuf

Đây là 2 lớp mà độ dài chuỗi phải biết lúc compile. Ví dụ:

TBuf myBuf; // có thể chỉnh sửa trên myBuf
TBufC herBuf; // không thể chỉnh sửa trên herBuf

Cách bố trí bên trong TBufC: [iLength: 4byte] [2byte] [2byte] [2byte] [2byte] [2byte] … (iLength lần 2byte)

Cách bố trí bên trong TBuf: [iLength: 4byte] [iMaxLength: 4byte] [2byte] [2byte] [2byte] [2byte] [2byte] … (iMaxLength lần 2byte)

Thông thường, nên tạo 2 lớp này trên Stack.

Lớp HBufC

HBufC không bắt đầu bằng chữ T (nhưng cũng không kế thừa CBase). Hàm ý dữ liệu chuỗi không nằm kèm theo bên trong. Lớp này được thiết kế để dữ liệu chỉ tạo trên Heap, constructor của nó bị private, phải dùng con trỏ để tạo instance qua các hàm tĩnh: New(), NewL(), NewLC(). Ví dụ:

HBufC* myhbuf = HBufC::New(100); // myhbuf.iLength sẽ bằng 0
_LIT(KBae, "Bae");
myhbuf->Copy(KBae); // myhbuf.iLength sẽ bằng 3

Sau này muốn thay đổi độ dài lớn hơn (không cho phép nhỏ hơn hoặc bằng, vì như thế vô ích), gọi:

myhbuf = myhbuf.ReAlloc(200); // myhbuf.iLength vẫn bằng 3, dữ liệu từ 0 đến 2 giữ nguyên

Mặc dù HBufC chứa dữ liệu chuỗi chỉ đọc, nhưng có thể thay thế toàn bộ dữ liệu chuỗi của nó bằng toán tử gán (đã được override) hoặc dùng hàm Alloc() của lớp TDesC. Toán tử gán sẽ copy dữ liệu từ bên ngoài vào trong HBufC. Hàm Alloc() sẽ tạo ra một con trỏ HBufC mới với cùng nội dung dữ liệu y chang.

Chú ý đọc thêm document về HBufC, có vài lắt léo về các hàm New(), ReAlloc() và toán tử gán.

Dùng HBufC khi độ dài dữ liệu chỉ được biết lúc runtime, tuy nhiên bạn nhớ delete nó khi không dùng nữa.

Cách bố trí bên trong HBufC: [iLength: 4byte] [con trỏ: 4byte]

HBufC chịu trách nhiệm về con trỏ trên (không như TPtrC hoặc TPtr).

Có điều hiện nay HBufC đã lỗi thời, người ta hay dùng RBuf hơn.

Lớp TPtr

Lớp này cũng như lớp TPtrC, ngoại trừ thêm khả năng chỉnh sửa dữ liệu. TPtr còn cho ta một cách kỳ diệu để chỉnh sửa dữ liệu chuỗi trên các lớp tận cùng bằng C: TBufC, HBufC:

TPtr ptr1 = herBuf->Des();
TPtr ptr2 = myhbuf->Des();

Đây là lý do tại sao không có lớp HBuf.

Cách bố trí bên trong TPtr: [iLength: 4byte] [iMaxLength: 4byte] [con trỏ: 4byte]

Lớp RBuf

RBuf có cái gì đó nằm giữa HBufC và TPtr, RBuf tự tạo ra dữ liệu chuỗi trên Heap (như HBufC) , đồng thời cho phép chỉnh sửa dữ liệu chuỗi (như TPtr). Chữ R ở đầu tức là RBuf hoạt động như một Resource Handler:

RBuf rbuf; // chưa có dữ liệu gì
rbuf.Create(400); // tạo dữ liệu chuỗi, iLength = 0, iMaxLength = 400
...
rbuf.Close(); // giải phóng dữ liệu

Cách bố trí bên trong RBuf: [iLength: 4byte] [iMaxLength: 4byte] [con trỏ: 4byte]

Macro _LIT và lớp TLitC

Lớp TLitC không kế thừa bất cứ lớp nào kể trên, tuy nhiên nó có cách bố trí dữ liệu giống hệt TBufC. Dữ liệu nằm cứng trên ROM, tức là được nhúng vào file thực thi.

TLitC override 3 operator sau:

inline const TDesC *operator &() const; // operator 1
inline operator const TDesC &() const; // operator 2
inline const TDesC &operator()() const; // operator 3

Nhờ vậy có thể viết:

_LIT(KHello, "Hello World"); // Tạo một đối tượng TLitC tên là KHello
TBuf buf;
buf.Copy(KHello); // theo operator 2

int len = KHello().Length(); // theo operator 3
TDesC* pDes = &KHello; // theo operator 1

Một số nhu cầu thường gặp:

Chuyển đổi từ 16 bit sang 8 bit hoặc ngược lại nhờ hàm Copy():

void Convert16to8(TDes16 & s, TDes8 & d)
{
	d.Zero();
	d.Copy(s);
}

Chuyển đổi Descriptor 8 bit sang char* nhờ hàm Ptr():

char* DesToChar(TDes8& s)
{
	return (char*) s.Ptr();
}

Chuyển đổi từ char* sang Descriptor 8 bit nhờ lớp Ptr8:

TDes8* CharToDes(char* ch, int len)
{
	TPtr8 ptr = new TPtr8( (TUint8*) ch, len, len );
	return (TDes8*) ptr;
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s