Saturday, May 28, 2005

20. How do I use heap-based buffer descriptors?

Heap-based descriptors, HBufC<n>, can be used for dynamic allocation of string data whose size is not known at compile time. It should also be used to store string data too large for the stack. In effect, HBufC should be used where malloc’d data would be used in C.

Although the class representing these descriptors is HBufC, these descriptors are always referred to by pointer, HBufC*. The class doesn’t comply with the standard Symbian OS naming conventions, but then, it doesn’t really fit any of the standard Symbian OS types exactly. It is simply prefixed with H to indicate that the data is stored on the heap.

HBufC can be created using one of the static NewL() functions of the class. These may leave if there is insufficient memory available (the non-leaving version will return NULL). The heap buffers must be constructed using one of these methods or by using one of the Alloc() or AllocL() methods of the TDesC class to spawn an HBufC copy of any descriptor.

The ‘C’ suffix in the class name indicates that these descriptors are constant, although, in common with the stack-based non-modifiable buffer descriptors, the class has a set of assignment operators to allow the entire contents of the buffer to be replaced (as long as the new data does not exceed the length of the heap cell originally allocated). In addition, as with TBufC, the heap-based descriptors can be manipulated at runtime by creating a modifiable pointer descriptor, TPtr over the heap buffer, using the Des() method. For example:

_LIT(KFred, "Fred");
// Allocate an empty heap descriptor of max length 4
HBufC* heapBuf = HBufC::NewL(4);
// Copy in the contents of KFred
*heapBuf = KFred; // heapBuf now contains "Fred"
// Or, a more direct alternative:
HBufC* heapBuf = KFred().AllocL();

_LIT(KBert, "Bert");
TPtr ptr(heapBuf->Des()); // Modifiable TPtr over heapBuf data area
ptr = KBert(); // Copies "Bert" into heapBuf
delete heapBuf; // heapBuf deleted - ptr is now invalid

The heap descriptors can be created dynamically to the size required, but are not automatically resized when additional data storage is needed. Instead, you must make sure the buffer has sufficient memory before calling a modification operation. If necessary, you can reallocate the buffer, to grow it to the desired size, using the set of ReAllocL() methods. These may potentially move the buffer from its previous memory location. If there is insufficient memory, they will leave.

_LIT(KFred, "Fred");
// Allocate a heap descriptor of max length 4
HBufC* heapBuf = KFred().AllocLC();

_LIT(KCyril, "Cyril");
TPtr ptr(heapBuf->Des()); // Modifiable TPtr over heapBuf data area
ptr = KCyril(); // This would panic because max length (4) is exceeded

// Instead, we need to do a reallocation
// Leave on cleanup stack in case the realloc fails and a leave occurs
heapBuf = heapBuf->ReAllocL(5);

// Realloc succeeded, but heapBuf pointer may have changed
// We must update the pointer stored on the cleanup stack
CleanupStack::Pop(); // Push it off
// Push it back on again
CleanupStack::PushL(heapBuf);

// Since realloc may have changed the location in memory
// we must also reset ptr

ptr.Set(heapBuf->Des());

ptr = KCyril(); // Copies "Cyril" into heapBuf


CleanupStack::PopAndDestroy(heapBuf); // heapBuf destroyed


Moral of the story
Heap descriptors can be created dynamically but are not automatically resized should they need to be extended.

Comments:
Good question.

I suppose the biggest advantage of an HBuf is that you can leave determining its size until runtime. You can't allocate a TBuf dynamically - it's size must be fixed at compile time.

So, you could do this, for example:

void HeapBasedExampleL(TInt aVal)
{
TBuf<12>* heapBuf = new (ELeave) TBuf<12>;
heapBuf->Format(_L("%d"), aVal);

... // Do something with heapBuf, that doesn't leave, so no cleanup stack required

delete heapBuf;
}

But the size of TBuf is fixed. You can't do this:

void HeapBasedExampleL(TInt aVal)
{
TBuf<aVal>* heapBuf = new (ELeave) TBuf<aVal>;
}

So you can use TBuf/TBufC on the heap if you know the size required in advance.

Another useful way of having a heap-based TBuf, without actually having to explicitly alloc it on the heap, is to make it a member of a CBase-derived class. It will be put on the heap 'for free' when the owner object is created. This is handy when you know the size of the buffer is larger than recommended for placing on the stack.
 
Is it safe to call ReAllocL on an HBufC that is on the cleanup stack?
 
Consider using an RBuf if you need to realloc a HBuf that is on the cleanupstack. RBuf became available in Symbian OS 8.0. Basically, use RBuf where you would have used HBufC and where the content is expected to change frequently.
For earlier versions, the algorithms I've seen that ReAlloc a HBufC on the cleanup stack assume the HBufC is the last item on the stack and thus can be popped.
 
Hi, you have a typo, the code is:

CleanupStack::Pop(heapBuf);

with a comment that its deleted.

But of course only if PopAndDestroy() is called does it get deleted.



Regarding the use of Realloc() with the cleanup stack, it should always be safe to pop it off the stack, the problem comes if the address was changed during the re-alloc, in which case if its popped and destoryed the attempt to delete it could blow up as the memory address on the stack may no longer be allocated or may point to something else.

So if a HBufC is on the stack and is re-alloced if you do this:
CleanupStack::Pop();
CleanupStack::PushL(hBuf);

It makes things safe.
 
This comment has been removed by the author.
 
This code is still incorrect, if the address gets altered during the reallocation then this line will panic:

CleanupStack::PopAndDestroy(heapBuf); // heapBuf deleted - ptr is now invalid
 
Yes, you're right. I failed to update the code, or somehow managed to remove the updates. I've fixed it now - thanks for pointing it out
 
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?

Google
WWW Descriptors FAQ