Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

106

DYNAMIC ARRAYS AND BUFFERS

the array must occur at the end and that they do not support ordering and matching.

When working with fixed-length arrays, use TFixedArray instead of a generic C++ array to take advantage of bounds-checking and the utility functions provided.

7.6 Dynamic Buffers

Dynamic buffers are useful for storing binary data when its size is not fixed at compile time and it may need to expand to a potentially significant size at runtime. Descriptors or C++ arrays can be used to store binary data on Symbian OS, but these are not dynamically extensible; that is, a fixed-length C++ array cannot be expanded and a descriptor will panic if you attempt to write off the end of the array. You can use a heap descriptor, HBufC, and a modifiable pointer to write into it but even then you must manage the allocation of memory when you need to expand the array (as described in Chapter 5).

Dynamic buffers provide an alternative solution for binary data, but you should beware of using these classes as an alternative to descriptors for text data. The descriptor classes have been optimized for that purpose; in addition, dynamic buffer classes store data in 8-bit buffers, so you cannot use them comfortably for 16-bit Unicode strings.

When I described the underlying memory layout of the dynamic array classes, I mentioned that they use the CBufBase-derived classes CBufFlat and CBufSeg. CBufBase is an abstract class that provides a common interface to the dynamic buffers. This includes methods to insert, delete, read and write to the buffer. CBufFlat and CBufSeg are the concrete dynamic buffer classes. They are straightforward to use, as you’ll see from the example code below. When instantiating an object using the static NewL() function, a granularity must be specified, which is the number of bytes by which the buffer will be reallocated when it needs to be resized. For a segmented buffer, the granularity determines the size of a segment.

Operations on dynamic buffers are all specified in terms of a buffer position, which is an integer value indicating a byte offset into the buffer data, and thus has a valid range from zero to the size of the buffer. The InsertL() and DeleteL() functions shuffle the data in the buffer after the insertion point. For this reason, any pointers to data in the dynamic buffers must be discarded when the data in the buffer is updated by insertion or deletion. As an example, consider an insertion into a flat

DYNAMIC BUFFERS

107

buffer. This may potentially cause it to be reallocated, thus invalidating any pointers to data in the original heap cell. Likewise, deletion of buffer data causes data after the deletion point to move up the buffer. For this reason, it is sensible to reference data in the dynamic buffers only in terms of the buffer position.

Let’s take a look at some example code for the dynamic buffers which stores 8-bit data received from a source of random data. The details of the example are not that important; in fact it’s somewhat contrived, and its main purpose is to illustrate the use of the InsertL(), Delete(),

Compress(), ExpandL(), Read(), and Write() functions.

// Returns random data in an 8-bit heap descriptor of length = aLength HBufC8* GetRandomDataLC(TInt aLength); // Defined elsewhere

void PrintBufferL(CBufBase* aBuffer)

{

aBuffer->Compress();

// Compress to free unused memory at the end of a segment TInt length = aBuffer->Size();

HBufC8* readBuf = HBufC8::NewL(length); TPtr8 writable(readBuf->Des()); aBuffer->Read(0, writable);

... // Omitted. Print to the console delete readBuf;

}

void TestBuffersL()

{

__UHEAP_MARK; // Heap checking macro to test for memory leaks

CBufBase* buffer = CBufSeg::NewL(16); // Granularity = 16

CleanupStack::PushL(buffer); // There is no NewLC() function

HBufC8* data = GetRandomDataLC(32);// Data is on the cleanup stack

buffer->InsertL(0, *data);

// Destroy original. A copy is now stored in the buffer CleanupStack::PopAndDestroy(data);

PrintBufferL(buffer);

buffer->ExpandL(0, 100); // Pre-expand the buffer

TInt pos = 0;

for (TInt index = 0; index <4; index++, pos+16)

{// Write the data in several chunks data = GetRandomDataLC(16); buffer->Write(pos, *data);

CleanupStack::PopAndDestroy(data); // Copied so destroy here

}

PrintBufferL(buffer);

 

CleanupStack::PopAndDestroy(buffer);

// Clean up the buffer

__UHEAP_MARKEND;

// End of heap checking

}

 

108

DYNAMIC ARRAYS AND BUFFERS

The dynamic buffer, of type CBufSeg, is instantiated at the beginning of the TestBuffersL() function by a call to CBufSeg::NewL(). A 32-byte block of random data is retrieved from GetRandomDataLC() and inserted into the buffer at position 0, using InsertL().

The example also illustrates how to pre-expand the buffer using ExpandL() to allocate extra space in the buffer at the position specified (alternatively, you can use ResizeL(), which adds extra memory at the end). These methods are useful if you know there will be a number of insertions into the buffer and you wish to make them atomic. ExpandL() and ResizeL() perform a single allocation, which may fail, but if the buffer is expanded successfully then data can be added to the array using Write(), which cannot fail. This is useful to improve performance, since making a single call which may leave (and thus may need to be called in a TRAP) is far more efficient than making a number of InsertL() calls, each of which needs a TRAP.2 Following the buffer expansion, random data is retrieved and written to the buffer in a series of short blocks.

The PrintBufferL() function illustrates the use of the Compress(), Size() and Read() methods on the dynamic buffers. The Compress() method compresses the buffer to occupy minimal space, freeing any unused memory at the end of a segment for a CBufSeg, or the end of the flat contiguous buffer for CBufFlat. It’s a useful method for freeing up space when memory is low or if the buffer has reached its final size and cannot be expanded again. The example code uses it in PrintBufferL() before calling Size() on the buffer (and using the returned value to allocate a buffer of the appropriate size into which data from Read() is stored). The Size() method returns the number of heap bytes allocated to the buffer, which may be greater than the actual size of the data contained therein, because the contents of the buffer may not fill the total allocated size. The call to Compress() before Size() thus retrieves the size of the data contained in the buffer rather than the entire memory size allocated to it.

To retrieve data from the buffer, you can use the Ptr() function, which returns a TPtr8 for the given buffer position up to the end of the memory allocated. For a CBufFlat this returns a TPtr8 to the rest of the buffer, but for CBufSeg it returns only the data from the given position to the end of that segment. To retrieve all the data in a segmented buffer using Ptr(), you must use a loop which iterates over every allocated segment. Alternatively, as I’ve done in PrintBufferL(), the Read() method transfers data from the buffer into a descriptor, up to the length of the descriptor or the maximum length of the buffer, whichever is smaller.

2 In addition, for CBufFlat, multiple calls to InsertL() will fragment the heap whereas a single ExpandL() or ResizeL() call will allocate all the memory required in a single contiguous block.

SUMMARY

109

7.7 Summary

This chapter discussed the use of the Symbian OS dynamic array classes, which allow collections of data to be manipulated, expanding as necessary as elements are added to them. Unlike C++ arrays, dynamic arrays do not need to be created with a fixed size. However, if you do know the size of a collection, Symbian OS provides the TFixedArray class to represent a fixed-length array which extends simple C++ arrays to provide bounds-checking.

The chapter described the characteristics and use of the dynamic container classes RArray and RPointerArray and also discussed the CArrayX classes which the RArray classes supersede. The RArray classes were introduced to Symbian OS for enhanced performance over the CArrayX classes. They have a lower overhead because they do not construct a TPtr8 for each array access, have fewer assertion checks and no leaving methods and are implemented as R classes, which tend to have a lower overhead than C classes. The RArray classes also have improved search and sort functionality and should be preferred over CArrayX classes. However, the CArrayX classes may still be useful when dealing with variable-length elements or segmented memory, because there are no RArray analogues.

The chapter also discussed the descriptor array classes, CPtrC8Array and CPtrC16Array, and the dynamic buffers, CBufFlat and

CBufSeg.

All the dynamic array and buffer classes in Symbian OS are based on the thin template idiom (see Chapter 19). The use of lightweight templates allows the elements of the dynamic arrays to be objects of any type, such as pointers to CBase-derived objects, or T and R class objects.