- •Contents
- •1.1 Fundamental Types
- •1.2 T Classes
- •1.3 C Classes
- •1.4 R Classes
- •1.5 M Classes
- •1.6 Static Classes
- •1.7 Buyer Beware
- •1.8 Summary
- •2 Leaves: Symbian OS Exceptions
- •2.1 Leaving Functions
- •2.3 Constructors and Destructors
- •2.4 Working with Leaving Functions
- •2.5 Trapping a Leave Using TRAP and TRAPD
- •2.6 LeaveScan
- •2.7 Summary
- •3 The Cleanup Stack
- •3.1 Using the Cleanup Stack
- •3.2 How Does the Cleanup Stack Work?
- •3.4 Using TCleanupItem for Customized Cleanup
- •3.5 Portability
- •3.6 An Incidental Note on the Use of Casts
- •3.7 Summary
- •5 Descriptors: Symbian OS Strings
- •5.3 Pointer Descriptors
- •5.4 Stack-Based Buffer Descriptors
- •5.5 Heap-Based Buffer Descriptors
- •5.6 Literal Descriptors
- •5.7 Summary
- •6 Good Descriptor Style
- •6.1 Descriptors as Parameters and Return Types
- •6.2 Common Descriptor Methods
- •6.3 The Use of HBufC Heap Descriptors
- •6.4 Externalizing and Internalizing Descriptors
- •6.5 The Overuse of TFileName
- •6.6 Useful Classes for Descriptor Manipulation
- •6.7 Summary
- •7 Dynamic Arrays and Buffers
- •7.1 CArrayX Classes
- •7.3 Why Use RArray Instead of CArrayX?
- •7.4 Dynamic Descriptor Arrays
- •7.5 Fixed-Length Arrays
- •7.6 Dynamic Buffers
- •7.7 Summary
- •8.1 Multitasking Basics
- •8.2 Event-Driven Multitasking
- •8.3 Working with Active Objects
- •8.4 Example Code
- •8.5 Threads Without an Active Scheduler
- •8.6 Application Code and Active Objects
- •8.7 Summary
- •9 Active Objects under the Hood
- •9.1 Active Object Basics
- •9.2 Responsibilities of an Active Object
- •9.3 Responsibilities of an Asynchronous Service Provider
- •9.4 Responsibilities of the Active Scheduler
- •9.5 Starting the Active Scheduler
- •9.6 Nesting the Active Scheduler
- •9.7 Extending the Active Scheduler
- •9.8 Cancellation
- •9.9 Request Completion
- •9.10 State Machines
- •9.11 Long-Running Tasks
- •9.14 Common Mistakes
- •9.15 Summary
- •10 Symbian OS Threads and Processes
- •10.2 Thread Priorities
- •10.3 Stopping a Running Thread
- •10.5 Exception Handling
- •10.6 Processes
- •10.7 Summary
- •11.2 How Do the Client and Server Fit Together?
- •11.3 How Do the Client and Server Communicate?
- •11.5 How Do Synchronous and Asynchronous Requests Differ?
- •11.6 How Is a Server Started?
- •11.7 How Many Connections Can a Client Have?
- •11.8 What Happens When a Client Disconnects?
- •11.9 What Happens If a Client Dies?
- •11.10 What Happens If a Server Dies?
- •11.15 How Many Outstanding Requests Can a Client Make to a Server?
- •11.16 Can Server Functionality Be Extended?
- •11.17 Example Code
- •11.18 Summary
- •12.2 Client Boilerplate Code
- •12.3 Starting the Server and Connecting to It from the Client
- •12.4 Server Startup Code
- •12.5 Server Classes
- •12.6 Server Shutdown
- •12.7 Accessing the Server
- •12.8 Summary
- •13 Binary Types
- •13.1 Symbian OS EXEs
- •13.2 Symbian OS DLLs
- •13.3 Writable Static Data
- •13.4 Thread-Local Storage
- •13.5 The DLL Loader
- •13.6 UIDs
- •13.8 Summary
- •14 ECOM
- •14.1 ECOM Architecture
- •14.2 Features of an ECOM Interface
- •14.3 Factory Methods
- •14.4 Implementing an ECOM Interface
- •14.5 Resource Files
- •14.6 Example Client Code
- •14.7 Summary
- •15 Panics
- •15.2 Good Panic Style
- •15.3 Symbian OS Panic Categories
- •15.4 Panicking Another Thread
- •15.5 Faults, Leaves and Panics
- •15.6 Summary
- •16 Bug Detection Using Assertions
- •16.3 Summary
- •17 Debug Macros and Test Classes
- •17.1 Heap-Checking Macros
- •17.2 Object Invariance Macros
- •17.3 Console Tests Using RTest
- •17.4 Summary
- •18 Compatibility
- •18.1 Forward and Backward Compatibility
- •18.2 Source Compatibility
- •18.3 Binary Compatibility
- •18.4 Preventing Compatibility Breaks
- •18.5 What Can I Change Without Breaking Binary Compatibility?
- •18.6 Best Practice: Planning for Future Changes
- •18.7 Compatibility and the Symbian OS Class Types
- •18.8 Summary
- •19 Thin Templates
- •20.1 Class Layout
- •20.3 Parameters and Return Values
- •20.4 Member Data and Functional Abstraction
- •20.5 Choosing Class, Method and Parameter Names
- •20.7 Summary
- •21 Good Code Style
- •21.1 Reduce the Size of Program Code
- •21.2 Use Heap Memory Carefully
- •21.3 Use Stack Memory Carefully
- •21.5 Optimize Late
- •21.6 Summary
- •Glossary
- •Bibliography and Online Resources
- •Index
LITERAL DESCRIPTORS |
69 |
5.6 Literal Descriptors
Let’s move on to take a look at literal descriptors (constant descriptors that are compiled into ROM), which are equivalent to static char[] in C. Literal descriptors can be created by a set of macros defined in e32def.h. It’s a bit unnerving at first sight, so I’ve only included the explicit definitions for 8- and 16-bit literals. The implicit definitions for the neutral macros _L, _S and _LIT are exactly the same, where _L is equivalent to _L16 on a Unicode build and _L8 on a narrow ASCII build:
#define _L8(a) (TPtrC8((const TText8 *)(a))) #define _S8(a) ((const TText8 *)a)
#define _LIT8(name,s) const static TLitC8<sizeof(s)> name ={sizeof(s)-1,s}
#define _L16(a) (TPtrC16((const TText16 *)L ## a)) #define _S16(a) ((const TText16 *)L ## a)
#define _LIT16(name,s) const static TLitC16<sizeof(L##s)/2> name ={sizeof(L##s)/2-1,L##s}
Don’t worry; I’ll go through these slowly. Let’s look at _LIT macros first, since these are the most efficient, and preferred, Symbian OS literals. The typical use of the macro would be as follows:
_LIT(KMyLiteralDescriptor, "The quick brown fox jumps over the lazy dog");
KMyLiteralDescriptor can then be used as a constant descriptor, for example written to a file or displayed to a user. The _LIT macro builds a named object (KMyLiteralDescriptor) of type TLitC16 into the program binary, storing the appropriate string (in this case, The quick brown fox jumps over the lazy dog). As you’d expect, _LIT8 and
_LIT16 behave similarly. The reason why the macros subtract one byte for the length of the data and divide by two, in the case of _LIT16, is that the macro is converting the C byte string to data which can be used as a descriptor.
For reference, here’s the definition of class TLitC16, from e32des.h and e32des.inl, where __TText is typedef’d to a wide, 16-bit character. The TLitC8 class, which has an array of 8-bit characters, has a similar definition.
template <TInt S> class TLitC16
{
public:
inline const TDesC16* operator&() const; inline operator const TDesC16&() const; inline const TDesC16& operator()() const;
... // Omitted for clarity
70 |
DESCRIPTORS: SYMBIAN OS STRINGS |
public:
TUint iTypeLength;
__TText iBuf[__Align16(S)]; };
template <TInt S>
inline const TDesC16* TLitC16<S>::operator&() const {return REINTERPRET_CAST(const TDesC16*,this);}
template <TInt S>
inline const TDesC16& TLitC16<S>::operator()() const {return *operator&();}
template <TInt S>
inline TLitC16<S>::operator const TDesC16&() const {return *operator&();}
As you can see, TLitC16 (and TLitC8) do not derive from TDesC8 or TDesC16 but they have the same binary layouts as TBufC8 or TBufC16. This allows objects of these types to be used wherever TDesC is used.4
You can form a pointer descriptor from the literal as follows:
TPtrC8 thePtr(KMyLiteralDescriptor);
It’s slightly trickier to form a buffer descriptor from the literal. If you use sizeof() on a _LIT constant, the size of the corresponding TLitC object is returned, which is the size of the descriptor contents – in bytes – plus 8 extra bytes (a TUint for the stored length and the NULL terminator). If you want to use a stack-based buffer, you must take these extra bytes into account.
For a heap buffer, you can use the actual length of the descriptor contents to allocate the buffer then copy the contents of the descriptor. To get the correct length you can use the public iTypeLength member variable, or, more simply, use operator()() to reinterpret_cast the literal object to a descriptor and use the resultant object to determine the length of the contents. However, the simplest technique is to use operator()() to cast the literal object to a descriptor, then call one of the TDes::AllocL() methods upon it. For example:
// Define a 44 character literal
_LIT8(KExampleLit8, "The quick brown fox jumped over the lazy dog");
TInt size = sizeof(KExampleLit8); // 52 bytes (contents + 8 bytes) TInt descriptorLength = KExampleLit8.iTypeLength; // 44 bytes
// Form a stack buffer descriptor around the literal
4 Actually, the string stored in the program binary has a NULL terminator because the native compiler string is used to build it. However, as I described above, the length is adjusted to the correct value for a non-terminated descriptor by the _LIT macro as it constructs the TLitC object.
LITERAL DESCRIPTORS |
71 |
TBufC8<(sizeof(KExampleLit8)-8)> theStackBuffer(KExampleLit8);
//Create a heap buffer copying the contents of the literal HBufC8* theHeapBuffer = KExampleLit8().AllocL();
//Similar behaviour for wide literals
_LIT16(KExampleLit16, "The quick brown fox jumped over the lazy dog"); size = sizeof(KExampleLit16);// 96 bytes (contents in bytes + 8 bytes) descriptorLength = KExampleLit16.iTypeLength; // 44 bytes (contents)
Figure 5.4 illustrates the difference between the memory layouts for literal descriptors created using _L and _LIT.
_LIT (KHello, "Hello World!") |
|
TPtrC hello (_L("Hello World!")) |
|||||
|
|
|
|
|
|||
ROM |
StackTemporary |
ROM |
|||||
|
|
|
|
|
|
|
|
12 |
HelloWorld!\0 |
|
|
iLength |
iPtr |
|
Hello World!\0 |
|
|
12 |
|
||||
|
|
|
|
|
|
|
|
|
Figure 5.4 Memory layout for literal descriptors |
|
Incidentally, literals have already been defined in Symbian OS to represent a blank string. There are three variants of the ”NULL descriptor”, defined as follows:
Build independent: |
_LIT(KNullDesC,""); |
8-bit for non-Unicode strings: |
_LIT8(KNullDesC8,""); |
16-bit for Unicode strings: |
_LIT16(KNullDesC16,""); |
|
|
Let’s move on to look briefly at the _L and _S macros, the use of which is now deprecated in production code, though they may still be used in test code (where memory use is less critical). The advantage of using _L (or the explicit forms _L8 and _L16) is that you can use it in place of a TPtrC without having to declare it separately from where it is used (besides saving an extra line of code, one benefit is that you don’t have to think up a name for it!).
User::Panic(_L("example.dll"), KErrNotSupported);
The string (”example.dll”) is built into the program binary as a basic, NULL-terminated string, with no initial length member (unlike the TLitC built for the _LIT macro). Because there is no length word, the layout of the stored literal is not like that of a descriptor and, when the code executes, each instance of _L will result in construction of a temporary TPtrC, with the pointer set to the address of the first byte of the literal as it is stored in ROM. The use of such a run-time temporary is safe as long as it is used only during the lifetime of the function in which it
72 |
DESCRIPTORS: SYMBIAN OS STRINGS |
is created, or if it is copied for use outside of that lifetime.5 However, the construction of a temporary, which requires setting the pointer, the length and the descriptor type, is an overhead in terms of inline constructor code which may bloat binaries where many string literals are used.
The _S macro is equivalent to the _L macro in terms of the way the string is stored in the binary, but it does not construct the temporary TPtrC around the string. These macros are useful if you wish to use the literal directly as a NULL-terminated string; you will incur no overhead if you do so.
Prefer the use of _LIT to _L for declaring literal descriptors, because the latter has an overhead associated with constructing a run-time temporary TPtrC.
5.7 Summary
This chapter introduced Symbian OS descriptors and discussed the following:
•The descriptor model treats string and binary data in the same way because there is no reliance on trailing NULL terminators to indicate the length of string data.
•The descriptor classes offer native ”wide” 16-bit character support, but the same APIs can be used explicitly with 8-bit binary or string data.
•Non-modifiable descriptor functionality is defined by the TDesC base class and inherited by all its subclasses.
•For compactness, no virtual functions are used in the descriptor hierarchies, so no additional 4-byte vptr is added to each descriptor object.
•The base class, TDesC, defines Length() and uses the bottom 28 bits of the first machine word of the object to hold the length of the descriptor data. TDesC also defines Ptr() to access that data – it uses hardcoded logic to determine the correct memory address, based on the descriptor subtype which is indicated by the top 4 bits of the first machine word of the object.
5 Code like this will not work in a DLL because, although marked const, the creation of such a runtime temporary constitutes the use of modifiable static data, which is disallowed on Symbian OS (as described in Chapter 13).
const TPtrC KDefaultPath=_L("C:\\System\\Messages")
SUMMARY |
73 |
•Modifiable descriptor functionality is defined by the TDes class, which derives from TDesC, adding an extra machine word to store the maximum allowed length of a descriptor. The TDes class implements typical data modification operations.
•The five concrete descriptor classes can be subdivided in terms of their memory layout (pointer or buffer), whether they are constant or modifiable and whether they are heapor stack-based. However, there is significant interoperability between the classes, and the base class 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 the data area they reference. You, the programmer, are responsible for memory management.
•Descriptor member functions check that access to the descriptor lies within its data area and raise a panic (in both debug and release builds) if a descriptor overflow would result. This ensures that descriptor code is robust and that programming errors are easy to track down.
•_LIT literals are not part of the TDesC inheritance hierarchy, but have an equivalent memory layout in ROM to TBufC and can thus be used interchangeably.
•The advantage of using the _L literal macros is that you can use them where you would use a temporary TPtrC, without having to predefine and name them. The disadvantage is the extra run-time overhead associated with construction of a temporary descriptor – which is why they are deprecated in production code.
•Symbian OS descriptors may take some getting used to, but cannot be avoided when programming for Symbian OS because many API functions use them.
The next chapter shows how to use descriptors effectively and examines the more frequently used descriptor functions. It also describes some common descriptor mistakes and misconceptions.