- •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
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.