How to use the resizable buffer descriptor - RBuf

Use this descriptor to hold a string or binary data.

A resizable buffer descriptor is an RBuf type. Use the member functions of the base classes: TDes and TDesC to change the data in the descriptor. See Abstract base descriptor classes.

This buffer of the descriptor is put on the heap. The buffer is the place where the data is put. This is useful if you do not know the maximum size of the data until run time.

The resizable buffer descriptors are similar to what are called Heap descriptors, or HBufC types, but the API provided by RBuf is easier to use. The RBuf API also makes it easier to change the maximum size of the buffer containing the data, a task often referred to as reallocating the buffer.

The general guidelines are: use HBufC descriptors to contain data that rarely changes; use RBuf descriptors to contain data that changes frequently. However, in practice, RBuf types are equally suitable for both cases, and you should always consider using an RBuf in preference to an HBufC.

For cases where your code uses APIs that return an HBufC type, you can convert this to an RBuf. In effect, RBuf can act as a wrapper around the HBufC; access and manipulation of the data is then done through the member functions of RBuf and its base classes TDes and TDesC

Some key points about resizable buffer descriptors:

  • For text data, it is usual to construct an RBuf type and allow the appropriate variant, either a RBuf8 or a RBuf16 to be selected at build time.

  • For binary data, an explicit RBuf8 is used.

  • It is rare to use an explicit RBuf16.

  • Data can be changed through RBuf as well as replaced using its assignment operators. Like all descriptors, RBuf is derived from:

    • the TDes class, through which the data can be manipulated

    • the TDesC class, through which information about the data (such as its length) can be retrieved.

  • You can pass an RBuf to a function that takes a TDesC& parameter, and a TDes& parameter.

  • Memory that has already been allocated by your code can be transferred to an RBuf. This allows any data that may be in that location to be managed through the descriptor.

  • Ownership of an existing HBufC can be transferred to an RBuf. This allows the data contained within the HBufC to be managed through the RBuf API.

Although the following notes refer to the build independent types, they are equally valid for the explicit 8-bit and 16-bit types.

Creating an RBuf

An RBuf object behaves like a handle to a resource. In this case, the resource is the buffer that contains the data. While this might seem to be an implementation issue, you need to understand that much of the interface provided by the RBuf class itself is involved in allocating, freeing and resizing this buffer.

An RBuf object can:

  • be placed on the program stack.

  • be a member of another 'C' type class.

An RBuf object cannot be a member of a 'T' type class.

See also Class types.

The default constructor does not allocate a buffer, and means that, by default, the object represents no data. In descriptor terminology, its length is zero and its maximum length is zero.

Before an RBuf can hold any data, you need to create the buffer.

Simple construction

The simplest way to create the buffer is to use the Create() or the CreateL() member functions of RBuf [See RBuf16::Create() and RBuf16::CreateL()].

For example, the following code fragment constructs a resizable buffer descriptor that can hold up to 15 data items. In descriptor terminology, this means that the maximum length is set to 15. The current length of the descriptor is set to zero, i.e. it contains no data.

RBuf buf;
...
buf.CreateL(15);
...

Note that CreateL() is similar to Create(), but leaves if memory cannot be allocated.

More advanced construction

A variation on the simple creation technique is to use the CreateMax() or the CreateMaxL() member functions of RBuf [See RBuf16::Create() and RBuf16::CreateL()].

These functions not only allocate the buffer, but set the current length of the data to be the same as the maximum length. For example:

RBuf buf;
...
buf.CreateMaxL(15);
...

No data has been assigned to the descriptor, and in effect it contains "arbitrary" data. However, this can be useful in cases where the descriptor needs to have a current length before calling another function. For example, you might want a buffer of 16 bytes initialised to binary zeroes:

RBuf8 buf;
...
buf.CreateMaxL(15);
buf.Fillz();
...

FillZ() provided by the TDes8 base class, sets the data to binary zeroes for the length of the descriptor, 15 bytes in this example. Note that we have explicitly used the 8-bit variant here.

There are other ways of achieving this, but this is an economical technique.

Creating an RBuf from an existing descriptor

A common requirement is to create an RBuf and to copy the data from an existing descriptor of any type. RBuf provides variants of Create() and CreateL() to do this.

In the following code fragment, a TBuf descriptor is passed to a function, which then passes it to this CreateL() variant. The source descriptor has a maximum length of 15, which means that the RBuf buffer is allocated so that the RBuf maximum length is also 15.

The content of the source descriptor is copied into the RBuf, and the length of the RBuf (i.e. the length of data that the descriptor represents) is set to be the same as that of the source descriptor - in this case 11.

_LIT(KSampleText,"Hello World");
...
TBuf<15> sampletext(KSampleText);
FuncL(sampletext);
...
void FuncL(TDesC& aSource)
    {
    RBuf buf;

    buf.CreateL(aSource);
    ...
    }

The following code fragment constructs a resizable buffer descriptor and copies data from a source descriptor. The length of data copied is limited by the value of the second parameter. If this is less than the length of the source descriptor, then only this length of data is copied. The buffer allocated is large enough to contain data of length specified by this second parameter.

This is often used in cases where the length of the source data is not easily predictable, and it is important to limit the size of the RBuf buffer created.

There are two possibilities in this code fragment :

  • if the length of aSource is 20, then the maximum length of buf is limited to 10, only half the data in aSource is copied, and the length of buf is set to 10.

  • if the length of aSource is 8, then the maximum length of buf is still set to 10; all 8 items are copied, and the length of buf is set to 8.

void FuncL(TDesC& aSource)
    {
    RBuf buf;
    ...
    buf.CreateL(aSource,10);
    ...
    }

Creating an RBuf by transferring ownership of a pre-existing buffer

An important technique is to create an RBuf and transfer ownership of existing allocated memory to it. This allows any data in that block of memory to be managed through the RBuf.

There are three main cases to consider:

  • transferring ownership of a preexisting heap descriptor, an HBufC type.

  • transferring ownership of allocated memory

  • transferring ownership of the buffer owned by a different RBuf.

Transferring ownership of an HBufC

This is a mechanism you would use if an existing API returned an HBufC, and you wanted to deal with its data through an RBuf instead.

The following code fragment shows how this could be done. Note that the HBufC pointer is passed to the RBuf constructor.

Following construction, ownership of the heap descriptor has been passed to the RBuf object. In descriptor terminology, the maximum length of the RBuf is 15, and its length is 11, reflecting the state of the original HBufC object.

You can now manipulate any data that may have been assigned to the original HBufC, through the functions in the RBuf base classes.

HBufC* hptr;
_LIT(KSampleText,"Hello World");
...
hptr = HBufC::NewL(15);    // Creates a heap descriptor which can hold up
...                        // to 15 data items. The current length is zero.
*hptr = KSampleText;     // Assigns some data to the heap descriptor.
...                        // The current length of the heap descriptor is now 11.
RBuf buf(hptr);            // Ownership of the heap descriptor is passed
...                        // to the RBuf during construction of the RBuf.

There is an alternative technique that allows you to assign ownership of the HBufC after construction of the RBuf using the Assign() function. This allows you to reuse the same RBuf object. For example:

HBufC* hptr;
_LIT(KSampleText,"Hello World");
...
hptr = HBufC::NewL(15);    // Creates a heap descriptor which can hold up
...                        // to 15 data items. The current length is zero.
*hptr = KSampleText;     // Assigns some data to the heap descriptor.
...                        // The current length of the heap descriptor is now 11.
RBuf buf;
...
buf.Assign(hptr);        // Ownership of the heap descriptor is passed
...                        // to the RBuf after construction of the RBuf.

Once ownership has been transferred, you must not access the data through the original HBufC pointer.

There is no mechanism for reversing the transfer of ownership. The freeing of the buffer can only be done through the RBuf. See Freeing the buffer.

Transferring ownership of allocated memory

In the following code fragment, ptr is assumed to contain the address of memory previously allocated. The code fragment shows how ownership of this memory can be transferred to the RBuf.

TUint16* ptr;
TInt length(32);
...                // Assume memory of length 32 has been allocated
                // and its address stored in ptr.

RBuf buf;
...
buf.Assign(ptr,length);
...                // The memory passed to the descriptor becomes the
                // descriptor's buffer. In descriptor terminology,
                // the maximum length of the RBuf is 32; its length
                // is zero, meaning that the descriptor represents
                // no data.

Transferring ownership of the buffer owned by a different RBuf

In the following code fragment, an RBuf is created, a buffer created for it, and then ownership is transferred to another RBuf.

RBuf bufSource;
RBuf bufTarget;
...
bufSource.CreateL(15);    // Creates memory buffer for the descriptor
                        // that can hold up to 15 data items. In descriptor
                        // terminology, the maximum length is set to 15 and
                        // the current length is set to 0.

bufTarget.Assign(bufSource); // Transfers ownership of the memory buffer
                             // to the bufTarget descriptor.
...

Re-allocating the memory buffer

You can change the size of the memory buffer, by using the ReAlloc() or ReAllocL() member functions of RBuf.

It does not matter how the original buffer was allocated, whether directly via Create(), CreateL(), CreateMax() or CReateMaxL(), or by transfer of ownership of an existing buffer (and this includes transfer from an HBufC). You do this if you intend to increase (or significantly decrease) the amount of data that the descriptor is to represent. Remember that the amount of memory allocated to the buffer is not automatically changed in response to changes in the amount of data.

In the following code fragment, an RBuf is created by copying from an existing descriptor. It is then reallocated so that its maximum length is doubled. Error conditions, such as out-of-memory conditions are ignored.

_LIT(KSampleText,"Hello World");
...
TBuf<15> sampletext(KSampleText);
FuncL(sampletext);
...
void FuncL(TDesC& aSource)
    {
    RBuf buf;
    Tint max;

    buf.CreateL(aSource);    // max size is 15, length is 11.
    max = buf.MaxLength() * 2;    
    buf.ReallocL(max);        // max size is now 30, but length is still 11.
    ...
    }

Freeing the buffer

You can free the memory buffer by calling ReAlloc() or ReAllocL() and passing a zero value.

_LIT(KSampleText,"Hello World");
...
TBuf<15> sampletext(KSampleText);
FuncL(sampletext);
...
void FuncL(TDesC& aSource)
    {
    RBuf buf;

    buf.CreateL(aSource);    // max size is 15, length is 11.
    buf.ReallocL(0);        // max size is now 0, length is 0, the memory
                            // buffer has been freed, and data
                            // has been thrown away.
    ...
    }

You can also use the Close() member function of RBuf. For example:

_LIT(KSampleText,"Hello World");
...
TBuf<15> sampletext(KSampleText);
FuncL(sampletext);
...
void FuncL(TDesC& aSource)
    {
    RBuf buf;

    buf.CreateL(aSource);    // max size is 15, length is 11.
    buf.Close();            // max size is now 0, length is 0, the memory
                            // buffer has been freed, and data
                            // has been thrown away.
    ...
    }

Cleanup rules

  • To avoid memory leaks when the RBuf object is placed on the stack, you should use the following programming pattern:

    {
    RBuf buf;
    buf.CleanupClosePushL();
    ...                                 // Use the RBuf
    CleanupStack::PopAndDestroy()    //remove from cleanup stack
                                    // and free the buffer to avoid memory 
    }

    The CleanupClosePushL() function puts a cleanup item onto the cleanup stack. The effect of this is to cause the class's Close() function to be called in the event of a leave occurring. In its turn, Close() simply frees off the buffer.

  • If an RBuf is a member of a class, then that class's destructor must remember to call either : ReAllocL(0) or Close().

  • An RBuf can be reused, but you must remember to call either : ReAllocL(0) or Close() before you assign other memory or another HBufC. Failure to do this will result in a memory leak.

  • You should not use an RBuf as a member of a 'T' type class. See Class types.

Replacing and modifying data

Data in an RBuf descriptor can be replaced. It can also be modified using the standard functionality provided by the TDes base class.

_LIT(KSampleText,"Hello World");
...
TBuf<15> sampletext(KSampleText);
FuncL(sampletext);
...
void FuncL(TDesC& aSource)
    {
    RBuf buf;

    buf.CreateL(aSource.MaxLength());    // Create the RBuf.
    buf = aSource;                        // Copy "Hello World" using the assignment
                                        // operator.

    buf.Delete(1,1);                    // Delete the 1st character.
    ...
    buf.Close();
    }