Descriptors survival guide

Introduction to Symbian strings, and advice on which concrete class to use.

Descriptors are Symbian strings

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.

Abstract base classes

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.

Fixed size descriptors

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.

Dynamic descriptors

If you want a dynamic descriptor, choose RBuf. RBuf allocates a heap buffer, and deallocates it when the Close() method is called.

The 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.

Pointing at a buffer you do not own

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

String literals

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.

You can pass a string literal directly to a function taking a 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();

Packaging objects in descriptors

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

The use of package descriptors is often encapsulated in the server's client side API. The target object is passed to the constructor of the package descriptor, as shown below:
// 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.

Copyright note

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.

Related concepts