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

82

GOOD DESCRIPTOR STYLE

methods shown above) for which you should explicitly use the 8-bit descriptor classes, using TText8 pointers as returned by the Ptr() operation to access characters. When working with 16-bit text (for example, Java strings), you should indicate the fact explicitly by using the TDesC16-derived classes, referencing individual characters with TText16 pointers.

As I described in Chapter 5, because descriptors do not use NULL terminators, they may be used with binary data as well as strings, which also allows code re-use within Symbian OS. Binary data is always considered to be 8-bit and you should use the 8-bit classes to manipulate it, using TInt8 or TUint8 to reference individual bytes.

6.3 The Use of HBufC Heap Descriptors

Having discussed some of the features of the descriptor classes, I’ll move on now to discuss some of the common mistakes made when using descriptors. First I’ll cover the creation and use of HBufC heap descriptors.

As I mentioned in Chapter 5, HBufC can be spawned from existing descriptors using the Alloc() or AllocL() overloads implemented by TDesC. Here is a contrived example which shows how to replace inefficient code with AllocL():

void CSampleClass::UnnecessaryCodeL(const TDesC& aDes)

{

iHeapBuffer = HBufC::NewL(aDes.Length()); TPtr ptr(iHeapBuffer->Des()); ptr.Copy(aDes);

...

// could be replaced by a single line iHeapBuffer = aDes.AllocL();

}

Another common way to introduce complexity occurs in the opposite direction, that is, the generation of TDesC& from a heap descriptor. A common mistake is to call the Des() method on the heap descriptor; this is not incorrect (it returns a TDes&), but it is clearer and more efficient simply to de-reference the HBufC pointer when a non-modifiable TDesC& is required:

const TDesC& CSampleClass::MoreAccidentalComplexity()

{

return (iHeapBuffer->Des());

// could be replaced more efficiently with

return (*iHeapBuffer);

}

THE USE OF HBufC HEAP DESCRIPTORS

83

Another subtle problem occurs when you allocate an HBufC and then call Des() on it to return a TPtr. If you recall, an HBufC object doesn’t have a maximum length word – since it is non-modifiable, it doesn’t need one. But the modifiable TPtr does. The length of the TPtr is set to the stored length of the HBufC, but where does the maximum length come from when you create it? In fact, when you call Des() on HBufC, it uses the maximum length of the heap cell in which the HBufC was allocated to set the maximum length of the returned TPtr.

The maximum length of the heap cell may not be exactly what you specified as the maximum length of the heap descriptor when you allocated it. This may happen because you didn’t specify a word-aligned maximum length (i.e. a multiple of 4 bytes) or because there was not enough space left over in the free cell from which the heap cell was allocated to create any other heap cells. The minimum size required for a heap cell is approximately 12 bytes and, if there are fewer bytes left over, your descriptor will be given the extra bytes too. (The former case of specifying an unaligned maximum length is much more common.) The end result in either case is that the maximum length of a TPtr returned from a call to HBufC::Des() may not be exactly the size you asked for when you allocated the heap descriptor; while it will not be truncated, it could be longer. Don’t get caught out by the fact that it may be larger than you expect – but, likewise, don’t expect that it is simply rounded up to the next word-aligned value. For example:

HBufC8* buf = HBufC8::NewLC(9);

TPtr8 ptr(buf->Des());

TInt maxLength = ptr.MaxLength(); // maxLength>9 but may not be 12

In practice this will be guaranteed to fill ptr with at least three extra bytes but beyond that, you cannot predict how much larger the maximum length of the heap buffer is than requested. Since you cannot guarantee the value of the maximum length, stick to using the Length() methods of the TPtr or HBufC, or the value you used to allocate the HBufC initially.

Here’s an example where you could get caught out, illustrating the use of pointers to manipulate the contents of a descriptor and the use of an assertion statement to catch access beyond the descriptor length:

_LIT(KPanic, "TestPointer"); const TInt KBufferLength = 10;

void TestPointer()

{// Create a buffer with length KBufferLength = 10 bytes HBufC8* myBuffer = HBufC8::NewMaxL(KBufferLength);

TPtr8 myPtr(myBuffer->Des()); myPtr.Fill(’?’); // Fill with ’?’

84

GOOD DESCRIPTOR STYLE

 

// Byte pointer to descriptor in memory

 

TUint8* ptr = (TUint8*)myPtr.Ptr();

 

TInt maxLength = myPtr.MaxLength();

 

for (TInt index = 0; index < maxLength; index++)

{// This fails at the end of the buffer (index = 10) // because myPtr.MaxLength() > KBufferLength __ASSERT_DEBUG(index<KBufferLength,

User::Panic(KPanic, KErrOverflow)); (*ptr) = ’!’; // Replace the contents with ’!’ ++ptr;

}

}

A common mistake is to call the Des() method on the heap descriptor to return a TDes&. It is more efficient simply to de-reference the HBufC pointer when a non-modifiable TDesC& is required.

6.4 Externalizing and Internalizing Descriptors

Moving on from the accidental complexity that may be introduced when using heap descriptors to a more general issue – that of externalizing descriptor data to file using a writable stream and re-internalizing it using a readable stream. Consider the following sample code:

//Writes the contents of iHeapBuffer to a writable stream void CSampleClass::ExternalizeL(RWriteStream& aStream) const

{

//Write the descriptor’s length aStream.WriteUint32L(iHeapBuffer->Length());

//Write the descriptor’s data aStream.WriteL(*iHeapBuffer, iHeapBuffer->Length());

}

//Instantiates iHeapBuffer by reading the contents of the stream void CSomeClass::InternalizeL(RReadStream& aStream)

{

TInt size=aStream.ReadUint32L(); // Read the descriptor’s length iHeapBuffer = HBufC::NewL(size); // Allocate iHeapBuffer

//Create a modifiable descriptor over iHeapBuffer

TPtr ptr(iHeapBuffer->Des());

// Read the descriptor data into iHeapBuffer

aStream.ReadL(ptr,size);

}

The code above implements descriptor externalization and internalization in a very basic manner, using four bytes to store the descriptor length, then adding the descriptor data to the stream separately. In the InternalizeL() method, the descriptor is reconstructed in four rather

EXTERNALIZING AND INTERNALIZING DESCRIPTORS

85

awkward stages. In fact, Symbian OS provides a templated stream operator (operator<<) for externalization, which compresses the length information to keep descriptor storage as efficient and compact as possible. Furthermore, descriptors externalized in this way may be re-created from the stream, as heap descriptors using the NewL() overloads of HBufC, passing in the read stream and an additional parameter to indicate the maximum length of data to be read from the stream. This is a significantly more efficient approach. To use operator<< you must link to estor.lib. The stream classes are documented in your preferred SDK.

void CSampleClass::ExternalizeL(RWriteStream& aStream) const {// Much more efficient, no wasted storage space aStream << iHeapBuffer;

}

void CSampleClass::InternalizeL(RReadStream& aStream)

{

iHeapBuffer = HBufC::NewL(aStream, KMaxLength);

}

You can also use the templated operator>>, which assumes that the descriptor was externalized using operator<<.

class TSomeClass

{

... // Omitted for clarity private:

TBuf<12> iBuffer;

...

};

void TSomeClass::ExternalizeL(RWriteStream& aStream)

{

aStream << iBuffer;

...

}

void TSomeClass::InternalizeL(RReadStream& aStream)

{

aStream >> iBuffer;

...

}

You’ll notice that the internalization and externalization methods above are leaving functions. This is because the templated streaming operators, operator>> and operator<<, may leave. They are not suffixed with L, as is conventional for all leaving functions, because they are operators, and to do so would interfere with their usage. The fact that they can leave isn’t picked up by the leave-checking tool LeaveScan (described in Chapter 2) either. When writing code which uses them you should make a special point of checking that your code is leave-safe and that functions are named according to Symbian OS.