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

6

Good Descriptor Style

For every problem there is one solution which is simple, neat and wrong

H L Mencken

The previous chapter covered the basics of Symbian OS strings, known as descriptors. It examined the methods used to instantiate each concrete descriptor class and described how to access, modify and replace the descriptor data. It also discussed the descriptor base classes (the nonmodifiable base class TDesC, which implements constant descriptor operations, and TDes, which derives from it and implements methods for descriptor modification). The chapter should have given you a good idea of the concrete types of descriptor.

This chapter examines some of the descriptor manipulation methods of the base classes and discusses mistakes and problems commonly encountered when using descriptors. Figure 6.1 summarizes the descriptor classes and the factors to bear in mind when deciding which type of descriptor to use.

6.1 Descriptors as Parameters and Return Types

When writing code, you probably don’t want to be constrained to using a TBuf just because a particular library function requires it. Likewise, as a function provider, you’re probably not interested in the type of descriptor your callers will be passing to you. In fact, you shouldn’t require a particular type, because if you change the implementation later, you may want to change the type of descriptor, and if you expose it at the API level you will require your clients to change their code. This kind of change breaks source compatibility and is highly undesirable. I’ll discuss compatibility further in Chapter 18.

Unless you’re taking ownership, you don’t even need to know if the incoming descriptor parameter or return value is stackor heap-based. In fact, as long as the descriptor is one of the standard types, so the

76

GOOD DESCRIPTOR STYLE

YES

Is the descriptor

NO

 

 

 

 

 

modifiable?

 

 

 

 

 

Has the memory for

 

YES

 

 

 

 

 

 

 

 

 

 

YES

 

Has the memory for

the descriptor data been

 

TPtr

 

 

 

TPtrC

 

the descriptor data been

allocated elsewhere (heap

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

allocated elsewhere

or stack)?

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(heap, stack

 

 

 

TPtr can modify data TPtrC can point to data

 

 

 

or ROM)?

 

 

 

stored in an HBufC,

 

 

stored in an HBufC,

 

 

 

 

 

 

 

 

 

 

 

 

 

TBufC or TBuf

 

 

TBufC, TBuf or as a

 

 

 

 

 

 

NO

 

 

 

 

 

 

literal descriptor in ROM

 

 

NO

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

TBuf

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

If binary data is contained in the descriptor,

 

 

 

 

 

 

 

HEAP

Will the memory allocated

the classes ending in 8 (e.g. TBuf8)

 

 

 

HBufC

 

 

 

 

 

 

 

 

 

 

should be used. If the data is explicitly

 

 

 

 

 

 

 

 

be stackor heap-based?

 

 

 

 

 

 

 

 

 

 

wide, the classes ending in "16" (e.g.

Useful when the size of

 

 

 

 

 

TBuf16) should be used.

 

 

 

 

 

 

 

 

the descriptor data is

 

 

 

 

 

Otherwise, the neutral descriptor classes

 

 

 

 

 

 

 

not known at compile

 

 

 

 

 

(which are implicitly wide) should be used.

 

 

 

 

 

 

 

 

 

 

time

 

 

 

STACK

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

TBufC

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 6.1 Flow chart to choose the correct descriptor type

appropriate descriptor methods can be called on it, the receiving code can remain blissfully ignorant as to its layout and location in memory. For this reason, when defining functions you should always use the abstract base classes as parameters or return values. For efficiency, descriptor parameters should be passed by reference, either as const TDesC& for constant descriptors or TDes& when modifiable. I’ll discuss good API design in detail in Chapter 20.

As an example, class RFile defines straightforward file read and write methods as follows:

IMPORT_C TInt Write(const TDesC8& aDes);

IMPORT_C TInt Read(TDes8& aDes) const;

For both methods, the input descriptor is explicitly 8-bit to allow for both string and binary data within a file. The descriptor to write to the

DESCRIPTORS AS PARAMETERS AND RETURN TYPES

77

file is a constant reference to a non-modifiable descriptor, while to read from the file requires a modifiable descriptor. The maximum length of the modifiable descriptor determines how much file data can be read into it, so the file server doesn’t need to be passed a separate parameter to describe the length of the available space. The file server fills the descriptor unless the content of the file is shorter than the maximum length, in which case it writes what is available into the descriptor. The resultant length of the descriptor thus reflects the amount of data written into it, so the caller does not need to pass a separate parameter to determine the length of data returned.1

When writing a function which receives a modifiable descriptor you don’t actually need to know whether it has sufficient memory allocated to it, since the descriptor methods themselves perform bounds checking and panic if the operation would overflow the descriptor. Of course, you may not always want the descriptor methods to panic if the caller’s descriptor data area is too short. Sometimes the caller cannot know until runtime the maximum length required and a panic is somewhat terminal. You should define clearly in your documentation what happens when the descriptor is not large enough. There are times when a more suitable approach would be to return the length required to the caller so it can take appropriate steps to allocate a descriptor of the correct length. For example:

HBufC* CPoem::DoGetLineL(TInt aLineNumber)

{// Code omitted for clarity. Allocates and returns a heap buffer

//containing the text of aLineNumber (leaves if aLineNumber is

//out of range)

}

void CPoem::GetLineL(TInt aLineNumber, TDes& aDes)

{

HBufC* line = DoGetLineL(aLineNumber);

CleanupStack::PushL(line);

//Is the descriptor large enough (4 bytes or more) to return an

//integer representing the length of data required?

if (aDes.MaxLength() < line->Length())

{

if (aDes.MaxLength() >= sizeof(TInt))

{// Writes the length required (TPckg is described later) TPckg<TInt> length(line->Length());

aDes.Copy(length);

}

//Leave & indicate that the current length is too short

1 For comparison purposes only, here’s a similar function from the Win32 Platform SDK, which uses a basic buffer (lpBuffer) for file reads and thus requires extra parameters to indicate the amount to read and the amount that was actually read:

BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer,

DWORD nNumberofBytesToRead, LPDWORD lpNumberofBytesRead, LPOVERLAPPED lpOverlapped);

78

GOOD DESCRIPTOR STYLE

User::Leave(KErrOverflow); // Leaves are described in Chapter 2

}

else

{

aDes.Copy(*line);

CleanupStack::PopAndDestroy(line);

}

}

An alternative approach is to allocate a heap buffer of the appropriate size for the operation and return it to the caller. The caller takes ownership of the heap buffer on return and is responsible for deleting it:

HBufC8* CPoem::GetLineL(TInt aLineNumber)

{

return (DoGetLineL(aLineNumber)); // As shown above

}

void PrintPoemLinesL(CPoem& aPoem)

{

HBufC* line;

FOREVER // See footnote 2

{

line = poem.NextLineL();

... // Do something with line (make it leave-safe if necessary) delete line;

}

}

When defining functions, use the abstract base classes as parameters and return values. For efficiency, pass them by reference, either as const TDesC& for constant descriptors or TDes& when modifiable.

6.2 Common Descriptor Methods

Moving on, let’s consider some of the methods implemented by the descriptor base classes, particularly those that may cause problems if misused. The SDK documentation covers the descriptor classes extensively, and you’ll find more information on each of the descriptor methods I

2 The FOREVER macro is defined in e32def.h as follows:

#define FOREVER for(;;)

The code will run in the loop until the end of the poem is reached. At that point NextLineL() will leave because CPoem::DoGetNextLineL() leaves to indicate the end of the poem. This breaks the code out of the loop, so it will not run forever, only until the end of the poem. Some poems, of course, do seem to go on forever. . .

COMMON DESCRIPTOR METHODS

79

discuss here, as well as others for seeking, comparison, extraction, string and character manipulation and formatting.

The base classes implement methods to access and manipulate the string data of deriving classes. The base classes do not themselves store the data. They have no public constructors save an implicit copy constructor. So you won’t attempt to instantiate them. Will you?

The TDesC base class implements Ptr() to provide access to the descriptor data, as described in Chapter 5. The method returns a pointer to the first character in the data array, allowing you to manipulate the descriptor contents directly, using C pointers, if you so wish.3 In doing so, you should take care not to write off the end of the descriptor data area. (One way to check that this kind of programming error does not occur is to use assertion statements, as described in detail in Chapter 16.) I’ve included an example of using a pointer to manipulate descriptor data later in this chapter, to illustrate an issue related to the maximum length of heap descriptors.

TDesC implements both Size() and Length() methods, and you should be careful to differentiate between them. The Size() method returns the number of bytes the descriptor occupies, while the Length() method returns the number of characters it contains. For 8-bit descriptors, this is the same thing, i.e. the size of a character is a byte. However, from Symbian OS v5u, the native character is 16 bits wide, which means that each character occupies two bytes. For this reason, Size() always returns a value double that of Length(). (As I described in Chapter 5, the length of a descriptor cannot be greater than 228 bytes because the top four bits of the length word are reserved to indicate the type of descriptor.)

The base class for modifiable descriptors, TDes, implements a MaxLength() method which returns the maximum length allowed for the descriptor. Beware of the SetMax() method, which doesn’t allow you to change the maximum length of the descriptor, thereby expanding or contracting the data area; instead, it sets the current length of the descriptor to the maximum length allowed.

You can use SetLength() to adjust the descriptor length to any value between zero and its maximum length (inclusively). The Zero() method also allows you to set the length to zero. If you thought that method might fill the contents of the descriptor with zeroes, you want the FillZ() method – which is overloaded so you can fill the entire descriptor or to a required length. If you don’t want to fill with zeroes, the overloaded Fill() methods permit you to choose which character to fill the descriptor.

3 TDes also has the PtrZ() method, which returns a pointer to the first character in the data array and appends a NULL terminator to the descriptor data so the returned pointer can be used directly as a C string. (The length of the descriptor must be less than its maximum allowable length in order for there to be sufficient space for the zero terminator.)

80

GOOD DESCRIPTOR STYLE

TDes implements

an overloaded set of Copy() methods, two of

which are shown below. (There are another six overloads of the Copy() function, which allow copying directly from a NULL-terminated string or from a pointer directly into descriptor data and which perform folding, collation or case adjustment as part of the copy.)

IMPORT_C void Copy(const TDesC8 &aDes);

IMPORT_C void Copy(const TDesC16 &aDes);

These methods copy the descriptor parameter into the data area of the descriptor on which they are called, setting its length to reflect the new data. The methods will panic if the maximum length of the receiving descriptor is shorter than the incoming data. The Copy() method is overloaded to take either an 8- or 16-bit descriptor. Thus, not only is it possible to copy a narrow-width descriptor onto a narrow-width descriptor and a wide descriptor onto a wide descriptor, but it is also possible to copy between descriptor widths, effecting a conversion of sorts in the process.

These methods are an example of where the implementation of the TDes8 class differs from the TDes16 class. As Figure 6.2 illustrates, the Copy() method implemented by TDes8 to copy an incoming wide 16-bit descriptor into a narrow descriptor strips out alternate characters, assuming them to be zeroes, that is, the data values should not exceed 255 (decimal). The Copy() method which copies a narrow descriptor into the data area is a straight data copy, however.

// Instantiate a narrow descriptor

TBuf8<3> cat(_L8("cat")); // _L is described in Chapter 5

//Instantiate a wide descriptor TBuf16<3> dog(_L16("dog"));

//Copy the contents of the wide descriptor into the narrow descriptor cat.Copy(dog); // cat now contains "dog"

cat

 

 

dog

C

A

T

 

 

 

 

D

\0

O

\0

G

\0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

TDes8

 

 

 

 

 

 

 

TDes16

 

 

 

 

 

 

 

cat.Copy(dog)

 

 

 

 

 

 

Strips \0 padding

 

 

 

cat

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

D

O

 

G

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 6.2 NULL characters are stripped out when wide characters are copied into a narrow descriptor

COMMON DESCRIPTOR METHODS

81

Conversely, for class TDes16, an incoming 16-bit descriptor can be copied directly onto the data area, but the Copy() method that takes an 8-bit descriptor pads each character with a trailing zero as part of the copy operation, as shown in Figure 6.3. The Copy() methods thus form a rudimentary means of copying and converting when the character set is encoded by one 8-bit byte per character and the last byte of each wide character is simply a NULL character padding.

//Instantiate a narrow descriptor TBuf8<5> small(_L8("small"));

//Instantiate a wide descriptor TBuf16<5> large(_L16("large"));

//Copy the contents of the narrow descriptor into the wide large.Copy(small); // large now contains "small"

small

 

 

 

 

 

 

large

 

 

 

 

 

 

 

 

 

 

DS

 

\oM

OA

L\o

GL

 

 

L

\0

A

\0

R

\0

G

\0

E

\0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

TDes8

 

 

 

 

 

large.Copy(small)

 

 

 

 

 

TDes16

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Adds \0 padding

 

 

 

 

 

 

 

 

 

 

large

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

S

\0

M

\0

A

\0

L

\0

L

\0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 6.3 NULL characters pad narrow characters when copied into a wide descriptor

To perform proper conversion in both directions between 16-bit Unicode and 8-bit, non-Unicode, character sets (or between Unicode and the UTF-7 and UTF-8 transformation sets) you should use the conversion library (charconv.lib) that Symbian OS provides (see header file charconv.h). You can also use the Character Conversion Plug-in Provider API to write plug-in DLLs to extend the range of foreign character sets beyond the ones already provided by Symbian OS. I’ll discuss the use of a framework and plug-ins, a commonly used Symbian OS idiom, in more detail in Chapter 13.

Symbian OS has been built as _UNICODE since the v5U release. The descriptor classes which end in 8 or 16 reflect the character size of the descriptor. The neutral versions, with no numerical indicator, are implicitly 16-bit and, for many descriptor operations, you can simply use this neutral version, which will, if nothing else, make your code more readable. However, there are occasions when you are dealing with text of a specific character size. For example, you may be working with 8-bit text (e.g. an ASCII text file or Internet email – or simply using the RFile::Read()and RFile::Write()