Introduction to Symbian strings, and advice on which concrete class to use.
In Symbian C++, string handling is done
using a set of classes known as descriptors. There are two main abstract
descriptor classes (TDesC16
and TDes16
) and six concrete classes (TPtr16
, TBuf16
, RBuf16
, TPtrC16
, TBufC16
, and HBufC16
).
Figure: The main descriptor classes
The character width of descriptor classes can be identified
from their names. If the class name ends in 8 (for example, TPtr8
) it has narrow (8-bit) characters, while a descriptor
class name ending with 16 (for example, TPtr16
) manipulates
16-bit character strings. The classes with no number in their name,
as in the diagram above, are typedef
'd to the character
width set by the platform, which is 16 bit.
TDesC
is the abstract base
class for all 16–bit descriptors. All the public member functions
of TDesC
are const
(i.e. they do
not modify the object on which they are invoked). The most common
use of TDesC
is to pass descriptors as function arguments,
for example:
// Any one of the six concrete descriptor classes // can be passed to this function, as well as a literal // created with the _LIT macro. void PrintAnyDescriptor( const TDesC& aLogEntry ) { _LIT( KOut, "%S \n" ); RDebug::Printf( KOut, &aLogEntry ); }
Note the use of a constant reference in the function.
Declaring a non-constant reference would prevent the function from
calling the const
member functions defined in TDesC
. If we had not used a reference at all we would have
lost the derived class member data. This 'slicing' would be disastrous
because it is the derived class that gives access to the string data.
So it is important always to pass descriptors by reference when using
the abstract base classes.
TDes
is an abstract
class derived from TDesC
. The majority of the public
member functions of TDes
are non-const, for example Append()
, Insert()
, and Replace()
. As with TDesC
, the most common use of TDes
is to pass a descriptor as a function argument. A function
parameter of type TDes&
indicates that the function
accepts any of TPtr
, TBuf
, and RBuf
.
If you want a small string with a fixed
maximum size, choose TBuf
. As this class inherits
from TDes
you have access to all the methods of TDesC
and TDes
. The main constraint when
using TBuf
is that you must specify a compile time
constant as the maximum length of the descriptor. Place this value
in the angle brackets when you declare the descriptor. For example:
// Formatting for Printf _LIT( KOut, "%S Length: %d Max: %d \n" ); // String literals _LIT( KHello, "hello" ); _LIT( KWorld, " world" ); // Specify maximum length of the TBuf as // the template parameter. This must be a compile // time constant. TBuf<12> buf( KHello ); // Call base class methods to manipulate the string. buf.Append( KWorld ); buf.UpperCase(); // Prints "HELLO WORLD, 11, 12". console->Printf( KOut, &buf, buf.Length(), buf.MaxLength() );
If a call to Append()
overruns the maximum length
of the descriptor a panic would result (USER-11). This is a general
rule for all descriptor classes: they immediately detect buffer overflow
and panic.
Class TBufC
is similar to TBuf
in that the length of the descriptor is specified at
declaration time. As TBufC
does not derive from TDes
you do not have access to TDes
methods
if you declare a TBufC
. In most cases it is preferable
to use the richer functionality of TBuf
.
As TBuf
and TBufC
objects are often stored
on the stack, they are only suitable for relatively small strings
of up to 256 bytes.
If you want a dynamic descriptor, choose RBuf
. RBuf
allocates a heap buffer, and
deallocates it when the Close()
method is called.
RBuf
class does not manage the size of the
buffer and re-allocate it if more memory is required for a particular
operation. If a modification method, such as Append()
, is called on an RBuf
object for which there is
insufficient memory available, a panic will occur. As a programmer,
you are responsible for re-allocating memory to the descriptor if
it is required, using the ReAllocL()
method. Here
is an example:// Format string for Printf() _LIT( KOut, "%S \n" ); // String literals _LIT( KHeapExample, "I am stored in the Heap" ); _LIT( KBiggerString, "I am stored in the Heap and there is more space" ); // Declare an RBuf. Unlike with TBuf and TBufC, // the maximum length of the RBuf can change. RBuf rbuf; // Allocate sufficient memory to store the initial string. rbuf.CreateL( KHeapExample ); // Add to the cleanup stack before calling a // leaving function. rbuf.CleanupClosePushL(); // Allocate more memory to store a longer string. rbuf.ReAllocL( KBiggerString().Length() ); // Copy the longer string into the buffer. rbuf.Copy( KBiggerString ); // Prints "I am stored in the Heap and there is more space" console->Printf( KOut, &rbuf ); // Clean up. Calls Close() on the RBuf. CleanupStack::PopAndDestroy();Class
HBufC
is also a dynamic descriptor that manages heap memory.
As with RBuf
the heap memory is not automatically
resized but it is possible to resize the buffer explicitly using HBufC::ReAllocL()
. Use RBuf
in preference
to HBufC
: it is more flexible and easier to use.Class TPtrC
is
the equivalent of using const char*
when handling
strings in C. The data can be accessed but not modified. The data
is separate from the descriptor object and is stored elsewhere, for
example, in ROM, on the heap or on the stack. The memory that holds
the data is not owned by the pointer descriptor. You might use a TPtrC
to extract a substring from another descriptor, or
to wrap up non descriptor data for a function requiring a descriptor
parameter.
Class TPtr16
is similar to TPtrC
but it allows modification of, as well as access to,
a data buffer stored elsewhere. In general it is safer to use TPtrC
unless you are sure you need to call one of the TDes
methods that TPtr
inherits.
Figure: Memory layout of pointer descriptors TPtrC and TPtr
The examples above have used the _LIT
macro to declare named string literals, for example _LIT(
KHello, "Hello World!" )
. This adds the 16 bit string literal
"Hello World!" to the program binary and associates the symbol KHello
with it so that it can be subsequently referenced.
The _LIT macro creates an object of type TLitC
, but
there is no need to use this class directly.
const TDesC&
:// Function accepts any descriptor, as well // as a literal created with the _LIT macro. void PrintAnyDescriptor( const TDesC& aLogEntry ); // Declare a literal. _LIT( KHello, "Hello World!" ); // Pass directly to the function with // no explicit conversion required. PrintAnyDescriptor( KHello );You can also call a
TDesC
method on a string literal, but you need to perform
an explicit conversion to type const TDesC&
.
The function call operator acts as a (somewhat cryptic looking) shorthand
for this conversion:// Declare a literal. _LIT( KHello, "Hello World!" ); // Call a TDesC method directly on // the literal. Does not compile. TUint length = KHello.Length(); // Does compile. Note the () after // the name of the literal. TUint length = KHello().Length();
There are three specialized descriptor classes known as package descriptors. They are useful for treating flat data objects as descriptors so they can be copied between threads in different processes, for example when using the client server framework. The classes are templated on the type to be packaged, so any T object can be enclosed in a package descriptor.
Figure: The package descriptor classes
// Example object to be wrapped in a package descriptor. TExample example(...); // Create a package descriptor to allow a T object to // be treated as a descriptor. TPckg<TExample> buf( example );
Class TPckgBuf
creates a copy of the object to be wrapped in a descriptor. The
copied object can be modified by the package descriptor leaving the
original object unchanged. In contrast TPckg
and TPckgC
refer to the existing instance of the target object. TPckgC
provides read only access to this object, but TPckg
also allows modifier methods to be called.
Some of the material in this topic is based with permission on a Symbian Foundation wiki article Descriptors which is part of the series The Fundamentals of Symbian C++. The version used was that available at http://developer.symbian.org/ on 3 November 2010. The wiki content is licensed under the Creative Commons Attribution-Share Alike 2.0 UK: England & Wales License.