|
|
|
Descriptors are both fundamental to Symbian OS, and an excellent example of the difference of approach between non-OO and OO designs.
In C, a string such as
char* hello="hello";causes some memory to be set aside containing the six characters
'h', 'e', 'l', 'l', 'o', '\0'. A function such as strlen() works
by scanning the bytes starting from the beginning, and counting until it
reaches the trailing null character. If you wish to extend the string, you need
to know how much memory is allocated to it.
We can represent the basic requirements for strings by two abstract
classes: TDesC and TDes.
TDesC is a constant, or non-modifiable, descriptor. It
has an address and a length. Using a class like this, you can do any
manipulations to a string, provided they do not alter the data. As a
consequence, the TDesC class has many non-modifying
functions.
TDes is a modifiable descriptor: it has, in addition,
a maximum length. This allows the data to be manipulated, extended or
contracted, provided it does not exceed the maximum length. As a consequence,
TDes has many modifying functions which allow string
manipulation.
It is a fundamental aspect of descriptors that they do not allow
modification to exceed the allocated length. Other classes are provided to
allow this, for instance CBufBase and derived classes. If a
TDes function causes overflow, a panic will occur.
Because TDesC and TDes define all the
functions needed to access and manipulate string and memory data, many
functions take a const TDesC& parameter if they need to access
data, or a TDes& parameter if they need to modify it.
The abstract descriptor classes have several implementations. The simplest are pointer descriptors.
TPtrC just has length and address: its representation
needs just two machine words. A TPtrC may be set up to describe
any existing data. A TPtr adds a maximum length, and so may be
used to describe a buffer which is perhaps only partially allocated.
TPtrC and TPtr are somewhat like C
char* pointers. But because the length is contained in the
descriptor itself, there is no need to scan for trailing null characters, or to
allocate room for them.
Buffer descriptors, TBufC and TBuf,
contain their data as part of themselves, like char[] arrays in
C.
These descriptor classes exploit C++'s template mechanism, using an integer parameter to specify the length.
Heap descriptors contain their data in heap cell. These are used
when you do not know the length required for a buffer at build time, but decide
it at run-time. This is like (char*) malloc(length+1) in C.
The non-modifiable heap descriptor type, HBufC, is
allocated on the heap. They are always referred to through an
HBufC*, rather than an HBufC directly:
The modifiable heap descriptor type, RBuf, can be
created on the stack, but contains a pointer to data on the heap. This is
similar to a TPtr pointer descriptor, but an RBuf
owns the data that it points to, and is responsible for freeing the memory when
it is closed. An RBuf object can allocate its own buffer, take
ownership of a pre-existing section of allocated memory, or take ownership of a
pre-existing heap descriptor.
RBuf is easier to use than HBufC, so
should generally be preferred. RBuf was only introduced in version
8.0, however, so older code and APIs use HBufC.
The complete descriptor class hierarchy is
It represents an elegant use of OO. The TBufCBase
class is used as an implementation convenience, which is also a frequent OO
idiom (though it should be used with caution).
Versions of the descriptors classes are available that store 16-bit
or 8-bit wide items. When using descriptors for strings, you should always use
classes such as TDes etc. These classes are typedefed
to use the 16-bit implementations, such as TDes16 etc.
Because descriptors may contain any data, including nulls, they may
also be used to refer to byte data and general buffers. In these contexts, you
should use the 8-bit implementations, such as TDes8,
directly.