- •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
10
Symbian OS Threads and Processes
Don’t disturb my circles!
Said to be the last words of Archimedes who was drawing geometric figures in the dust and became so absorbed that he snapped at a Roman soldier. The soldier became enraged, pulled out his sword and killed him
Chapters 8 and 9 discussed the role of active objects in multitasking code on Symbian OS. Active objects are preferred to threads for this role because they were designed specifically to suit the resource-limited hardware upon which Symbian OS runs. Multithreaded code has significantly higher run-time requirements compared to active objects: for example, a context switch between threads may be an order of magnitude slower than a similar switch between active objects running in the same thread.1 Threads tend to have a larger size overhead too, typically requiring 4 KB kernel-side and 8 KB user-side for the program stack, compared to active objects, which generally occupy only the size of the C++ object (often less than 1 KB).
One of the main differences between multitasking with threads and active objects is the way in which they are scheduled. Active objects multitask cooperatively within a thread and, once handling an event, an active object cannot be pre-empted by the active scheduler in which it runs. Threads on Symbian OS are scheduled pre-emptively by the kernel.
Pre-emptive scheduling of threads means that data shared by threads must be protected with access synchronization primitives such as mutexes or semaphores. However, despite this additional complexity, pre-emptive scheduling is sometimes necessary. Consider the case of two active objects, one assigned a high priority because it handles user-input events
1 A context switch between threads running in the same process requires the processor registers of the running thread to be stored, and the state of the thread replacing it to be restored. If a reschedule occurs between threads running in two separate processes, the address space accessible to the thread, the process context, must additionally be stored and restored.
152 |
SYMBIAN OS THREADS AND PROCESSES |
and another with a lower priority, which performs increments of a longrunning task when no other active object’s event handler is running on the thread. If the event handler of the lower-priority active object happens to be running when an event for the higher-priority active object completes, it will continue to run to completion. No pre-emption will occur and the low-priority active object effectively ”holds up” the handler of the high-priority object. This can make the user interface sluggish and unresponsive.
Typically, on Symbian OS, problems of this nature are avoided by careful analysis of each active object’s event handler, to ensure that event processing is kept as short as possible (described in Chapters 8 and 9). It would generally not be regarded as reason enough to multithread the code. However, there are occasions where the use of several threads may be necessary, say to perform a task which cannot be split into short-running increments. By implementing it in a separate thread, it can run asynchronously without impacting an application’s response to user interface events.
On Symbian OS, active objects multitask cooperatively within a thread and cannot be pre-empted by the active scheduler; threads are scheduled pre-emptively by the kernel.
10.1Class RThread
On Symbian OS, the class used to manipulate threads is RThread (you’ll notice that it’s an R class, the characteristics of which are described in Chapter 1). An object of type RThread represents a handle to a thread, because the thread itself is a kernel object. An RThread object can be used to create or refer to another thread in order to manipulate it (e.g. suspend, resume, panic or kill it) and to transfer data to or from it.
The RThread class has been modified quite significantly as part of the changes made for the new hard real-time kernel delivered in releases of Symbian OS v8.0. I’ll identify the main differences as I come to them. Most notably, a number of the functions are now restricted for use on threads in the current process only, whereas previous versions of Symbian OS allowed one thread to manipulate any other thread in the system, even those in other processes. The changes to Symbian OS v8.0 have been introduced to protect threads against abuse from potentially malicious code.
At the time of going to press, Symbian identifies the hard real-time kernel as ”EKA2” – which is a historical reference standing for ”EPOC2
2 Symbian OS was previously known as EPOC, and earlier still, EPOC32.
CLASS RThread |
153 |
Kernel Architecture 2” – and refers to the kernel of previous |
releases |
as EKA1. Throughout this chapter, and the rest of the book, I’ll use this nomenclature to distinguish between versions of Symbian OS v8.0 containing the new kernel and previous versions when discussing any API differences.
On Symbian OS v7.0, class RThread is defined as follows in e32std.h. I’ve included the entire class definition because I’ll mention many of its methods throughout this chapter.
class RThread : public RHandleBase
{
public:
inline RThread();
IMPORT_C TInt Create(const TDesC& aName, TThreadFunction aFunction, TInt aStackSize,TAny* aPtr,RLibrary* aLibrary,RHeap* aHeap, TInt aHeapMinSize,TInt aHeapMaxSize,TOwnerType aType);
IMPORT_C TInt Create(const TDesC& aName, TThreadFunction aFunction,
TInt aStackSize,TInt aHeapMinSize,TInt aHeapMaxSize,
TAny* aPtr,TOwnerType aType=EOwnerProcess);
IMPORT_C TInt Create(const TDesC& aName,TThreadFunction aFunction,
TInt aStackSize,RHeap* aHeap,TAny* aPtr,
TOwnerType aType=EOwnerProcess);
IMPORT_C TInt SetInitialParameter(TAny* aPtr);
IMPORT_C TInt Open(const TDesC& aFullName,
TOwnerType aType=EOwnerProcess);
IMPORT_C TInt Open(TThreadId aID,TOwnerType aType=EOwnerProcess); IMPORT_C TThreadId Id() const;
IMPORT_C void Resume() const;
IMPORT_C void Suspend() const;
IMPORT_C TInt Rename(const TDesC& aName) const;
IMPORT_C void Kill(TInt aReason);
IMPORT_C void Terminate(TInt aReason);
IMPORT_C void Panic(const TDesC& aCategory,TInt aReason); IMPORT_C TInt Process(RProcess& aProcess) const; IMPORT_C TThreadPriority Priority() const;
IMPORT_C void SetPriority(TThreadPriority aPriority) const; IMPORT_C TProcessPriority ProcessPriority() const;
IMPORT_C void SetProcessPriority(TProcessPriority aPriority) const; IMPORT_C TBool System() const;
IMPORT_C void SetSystem(TBool aState) const;
IMPORT_C TBool Protected() const;
IMPORT_C void SetProtected(TBool aState) const;
IMPORT_C TInt RequestCount() const;
IMPORT_C TExitType ExitType() const;
IMPORT_C TInt ExitReason() const;
IMPORT_C TExitCategoryName ExitCategory() const;
IMPORT_C void RequestComplete(TRequestStatus*& aStatus,
TInt aReason) const;
IMPORT_C TInt GetDesLength(const TAny* aPtr) const;
IMPORT_C TInt GetDesMaxLength(const TAny* aPtr) const;
IMPORT_C void ReadL(const TAny* aPtr,TDes8& aDes,
TInt anOffset) const;
IMPORT_C void ReadL(const TAny* aPtr,TDes16 &aDes,
TInt anOffset) const;
IMPORT_C void WriteL(const TAny* aPtr,const TDesC8& aDes, TInt anOffset) const;
154 |
SYMBIAN OS THREADS AND PROCESSES |
|
IMPORT_C void WriteL(const TAny* aPtr,const TDesC16& aDes, |
|
TInt anOffset) const; |
IMPORT_C void Logon(TRequestStatus& aStatus) const; IMPORT_C TInt LogonCancel(TRequestStatus& aStatus) const; IMPORT_C RHeap* Heap();
IMPORT_C void HandleCount(TInt& aProcessHandleCount,
TInt& aThreadHandleCount) const;
IMPORT_C TExceptionHandler* ExceptionHandler() const; IMPORT_C TInt SetExceptionHandler(TExceptionHandler* aHandler,
TUint32 aMask);
IMPORT_C void ModifyExceptionMask(TUint32 aClearMask,
TUint32 aSetMask);
IMPORT_C TInt RaiseException(TExcType aType);
IMPORT_C TBool IsExceptionHandled(TExcType aType);
IMPORT_C void Context(TDes8& aDes) const;
IMPORT_C TInt GetRamSizes(TInt& aHeapSize,TInt& aStackSize);
IMPORT_C TInt GetCpuTime(TTimeIntervalMicroSeconds& aCpuTime)
const;
inline TInt Open(const TFindThread& aFind, TOwnerType aType=EOwnerProcess);
};
The base class of RThread is RHandleBase, which encapsulates the behavior of a generic handle and is used as a base class throughout Symbian OS to identify a handle to another object, often a kernel object. As Chapter 1 discussed, CBase must always be the base class of a C class (if only indirectly), but RHandleBase is not necessarily always the base of an R class, although you’ll find that a number of Symbian OS R classes do derive from it (e.g. RThread, RProcess, RMutex and RSessionBase). Neither does RHandleBase share the characteristics of class CBase (such as a virtual destructor and zero-initialization through an overloaded operator new). Instead, class RHandleBase encapsulates a 32-bit handle to the object its derived class represents and exports a limited number of public API methods to manipulate that handle, namely
Handle(), SetHandle(), Duplicate() and Close().
You can use the default constructor of RThread to acquire a handle to the thread your code is currently running in, which can be used as follows:
RHeap* myHeap = RThread().Heap(); // Handle to the current thread's heap
// ...or...
_LIT(KClangerPanic, "ClangerThread");
// Panic the current thread with KErrNotFound RThread().Panic(KClangerPanic, KErrNotFound);
The handle created by the default constructor is actually a pseudohandle set to the constant KCurrentThreadHandle, which is treated specially by the kernel. If you want a ”proper” handle to the current thread