Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 19
Текст из файла (страница 19)
The TDes class implementstypical data modification operations.• The five concrete descriptor classes can be subdivided in terms oftheir memory layout (pointer or buffer), whether they are constantor modifiable and whether they are heap- or stack-based. However,there is significant interoperability between the classes, and the baseclass APIs make no assumptions about their underlying data layout.Descriptor data can be stored in RAM (on the stack or heap) or ROM;the APIs are consistent regardless of location or memory layout.• For efficiency reasons, descriptors do not dynamically extend thedata area they reference. You, the programmer, are responsible formemory management.• Descriptor member functions check that access to the descriptor lieswithin its data area and raise a panic (in both debug and release builds)if a descriptor overflow would result.
This ensures that descriptor codeis robust and that programming errors are easy to track down.• _LIT literals are not part of the TDesC inheritance hierarchy, buthave an equivalent memory layout in ROM to TBufC and can thusbe used interchangeably.• The advantage of using the _L literal macros is that you can use themwhere you would use a temporary TPtrC, without having to predefineand name them.
The disadvantage is the extra run-time overheadassociated with construction of a temporary descriptor – which iswhy they are deprecated in production code.• Symbian OS descriptors may take some getting used to, but cannotbe avoided when programming for Symbian OS because many APIfunctions use them.The next chapter shows how to use descriptors effectively and examinesthe more frequently used descriptor functions. It also describes somecommon descriptor mistakes and misconceptions.6Good Descriptor StyleFor every problem there is one solution which is simple, neatand wrongH L MenckenThe previous chapter covered the basics of Symbian OS strings, knownas descriptors. It examined the methods used to instantiate each concretedescriptor class and described how to access, modify and replace thedescriptor data.
It also discussed the descriptor base classes (the nonmodifiable base class TDesC, which implements constant descriptoroperations, and TDes, which derives from it and implements methods fordescriptor modification). The chapter should have given you a good ideaof the concrete types of descriptor.This chapter examines some of the descriptor manipulation methodsof the base classes and discusses mistakes and problems commonlyencountered when using descriptors.
Figure 6.1 summarizes the descriptor classes and the factors to bear in mind when deciding which type ofdescriptor to use.6.1 Descriptors as Parameters and Return TypesWhen writing code, you probably don’t want to be constrained to using aTBuf just because a particular library function requires it. Likewise, as afunction provider, you’re probably not interested in the type of descriptoryour callers will be passing to you. In fact, you shouldn’t require aparticular type, because if you change the implementation later, you maywant to change the type of descriptor, and if you expose it at the APIlevel you will require your clients to change their code. This kind ofchange breaks source compatibility and is highly undesirable.
I’ll discusscompatibility further in Chapter 18.Unless you’re taking ownership, you don’t even need to know if theincoming descriptor parameter or return value is stack- or heap-based.In fact, as long as the descriptor is one of the standard types, so the76GOOD DESCRIPTOR STYLEYESIs the descriptormodifiable?NOHas the memory forHas the memory forYESYESthe descriptor data beenthe descriptor data beenTPtrTPtrCallocated elsewhere (heapallocated elsewhereor stack)?(heap, stackTPtr can modify data TPtrC can point to dataor ROM)?stored in an HBufC,stored in an HBufC,TBufC, TBuf or as aTBufC or TBufliteral descriptor in ROMNONOTBufIf binary data is contained in the descriptor,HEAP Will the memory allocatedthe classes ending in 8 (e.g.
TBuf8)HBufCbe stack- or heap-based?should be used. If the data is explicitlywide, the classes ending in "16" (e.g.Useful when the size ofTBuf16) should be used.the descriptor data isOtherwise, the neutral descriptor classesnot known at compile(which are implicitly wide) should be used.timeSTACKTBufCFigure 6.1 Flow chart to choose the correct descriptor typeappropriate descriptor methods can be called on it, the receiving codecan remain blissfully ignorant as to its layout and location in memory. Forthis reason, when defining functions you should always use the abstractbase classes as parameters or return values. For efficiency, descriptorparameters should be passed by reference, either as const TDesC& forconstant descriptors or TDes& when modifiable.
I’ll discuss good APIdesign in detail in Chapter 20.As an example, class RFile defines straightforward file read and writemethods 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 forboth string and binary data within a file. The descriptor to write to theDESCRIPTORS AS PARAMETERS AND RETURN TYPES77file is a constant reference to a non-modifiable descriptor, while to readfrom the file requires a modifiable descriptor. The maximum length ofthe modifiable descriptor determines how much file data can be readinto it, so the file server doesn’t need to be passed a separate parameterto describe the length of the available space. The file server fills thedescriptor unless the content of the file is shorter than the maximumlength, in which case it writes what is available into the descriptor.The resultant length of the descriptor thus reflects the amount of datawritten into it, so the caller does not need to pass a separate parameter todetermine the length of data returned.1When writing a function which receives a modifiable descriptor youdon’t actually need to know whether it has sufficient memory allocated toit, since the descriptor methods themselves perform bounds checking andpanic if the operation would overflow the descriptor.
Of course, you maynot always want the descriptor methods to panic if the caller’s descriptordata area is too short. Sometimes the caller cannot know until runtime themaximum length required and a panic is somewhat terminal. You shoulddefine clearly in your documentation what happens when the descriptor isnot large enough.
There are times when a more suitable approach wouldbe to return the length required to the caller so it can take appropriatesteps 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 short1For 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 toindicate the amount to read and the amount that was actually read:BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer,DWORD nNumberofBytesToRead, LPDWORD lpNumberofBytesRead,LPOVERLAPPED lpOverlapped);78GOOD DESCRIPTOR STYLEUser::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 appropriatesize for the operation and return it to the caller.
The caller takes ownershipof 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 parametersand return values. For efficiency, pass them by reference, either asconst TDesC& for constant descriptors or TDes& when modifiable.6.2 Common Descriptor MethodsMoving on, let’s consider some of the methods implemented by thedescriptor 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 I2The 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 pointNextLineL() will leave because CPoem::DoGetNextLineL() leaves to indicate theend of the poem. This breaks the code out of the loop, so it will not run forever, only untilthe end of the poem. Some poems, of course, do seem to go on forever. . .COMMON DESCRIPTOR METHODS79discuss here, as well as others for seeking, comparison, extraction, stringand character manipulation and formatting.The base classes implement methods to access and manipulate thestring data of deriving classes. The base classes do not themselves store thedata. 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 thedescriptor data, as described in Chapter 5. The method returns a pointerto the first character in the data array, allowing you to manipulate thedescriptor 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 occuris to use assertion statements, as described in detail in Chapter 16.) I’veincluded an example of using a pointer to manipulate descriptor datalater in this chapter, to illustrate an issue related to the maximum lengthof heap descriptors.TDesC implements both Size() and Length() methods, and youshould be careful to differentiate between them.
The Size() methodreturns 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 meansthat each character occupies two bytes. For this reason, Size() alwaysreturns a value double that of Length().