- •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
9
Active Objects under the Hood
Do not hack me as you did my Lord Russell
The last words of the Duke of Monmouth (1649–1685) addressed to his executioner
The previous chapter introduced active objects and described the basics, such as how to use them, how to derive a simple active object class and why active objects are used by Symbian OS as a lightweight alternative to threads for event-driven multitasking. This chapter considers active objects in more detail and discusses some commonly used active object idioms.
First of all, let’s examine in detail the responsibilities of active objects, asynchronous service providers and the active scheduler and walk through how they fit together. The previous chapter made the following main points:
•Symbian OS event-handling is usually managed in one thread, which runs a single active scheduler
•the active scheduler holds a set of active objects for that thread, each of which encapsulates an associated asynchronous service provider
•each thread has an associated request semaphore; when an asynchronous function completes, it generates an event by calling RequestComplete() on the requesting thread, which signals its semaphore and is detected by the active scheduler
•the active scheduler calls the RunL() event handler method of the active object associated with the completion event.
In the previous chapter, I used example code for an active object wrapper over an RTimer to illustrate the main points. You may find it useful to refer to that example throughout this chapter.
128 |
ACTIVE OBJECTS UNDER THE HOOD |
9.1 Active Object Basics
All active objects must derive from class CActive. On construction, each active object is assigned a priority value, which is set through a call to the base class constructor. In its constructor, the active object must also be added to the active scheduler through a call to CActiveScheduler::Add(). The active scheduler maintains a doubly-linked list of the active objects added to it, ordered by priority. When an active object is added to the active scheduler, it is added to that list in the appropriate position, according to its priority value.
As I described in Chapter 8, the active object encapsulates asynchronous functions (those that return immediately and complete at some later stage rather than returning only when the request has completed). On Symbian OS, asynchronous functions can be identified as those taking a TRequestStatus reference parameter into which the request completion status is posted. An object implementing such methods is usually known as an asynchronous service provider.
A typical active object class provides public ”request issuer” methods for its clients to submit asynchronous requests. These pass on the requests to the encapsulated asynchronous service provider, passing by reference the iStatus member variable of the CActive class as the TRequestStatus parameter. Having issued the request, the issuer method must call CActive::SetActive() to set the iActive member to indicate that there is an outstanding request.
The service provider must set the value of the incoming TRequestStatus to KRequestPending (=0x80000001) before acting on the request. Upon completion, if the service provider is in the same thread as the requester, it calls User::RequestComplete(), passing the TRequestStatus and a completion result, typically one of the standard errors such as KErrNone or KErrNotFound, to indicate the success or otherwise of the request. User::RequestComplete() sets the value of TRequestStatus and generates a completion event in the requesting thread by signaling the thread’s request semaphore. If the asynchronous service provider and the requester are in separate threads, the service provider must use an RThread object, representing a handle to the requesting thread, to complete the request. It should call
RThread::RequestComplete() to post the completion code and notify the request semaphore.
While the request is outstanding, the requesting thread runs in the active scheduler’s event processing loop. When it is not handling completion events, the active scheduler suspends the thread by calling User::WaitForAnyRequest(), which waits on a signal to the thread’s request semaphore. When the asynchronous service provider completes a request, it signals the semaphore of the requesting thread as described above, and the active scheduler determines which active
ACTIVE OBJECT BASICS |
129 |
object should handle the completed request. It uses its priority-ordered list of active objects, inspecting each one in turn to determine whether it has a request outstanding. It does so by checking the iActive flag; if the object does indeed have an outstanding request, it then inspects its TRequestStatus member variable to see if it is set to a value other than KRequestPending. If so, this indicates that the active object is associated with a request that has completed and that its event handler code should be called.
Having found a suitable active object, the active scheduler clears the active object’s iActive flag and calls its RunL() event handler. This method handles the event and may, for example, resubmit a request or generate an event on another object in the system. While this method is running, other events may be generated but RunL() is not pre-empted – it runs to completion before the active scheduler resumes control and determines whether any other requests have completed.
Once the RunL() call has finished, the active scheduler re-enters the event processing wait loop by issuing another User::WaitForAnyRequest() call. This checks the request semaphore and either suspends the thread (if no other requests have completed in the meantime) or returns immediately (if the semaphore indicates that other events were generated while the previous event handler was running) so the scheduler can repeat active object lookup and event handling.
Here’s some pseudo-code which represents the basic actions of the active scheduler’s event processing loop.
EventProcessingLoop()
{
//Suspend the thread until an event occurs
User::WaitForAnyRequest();
//Thread wakes when the request semaphore is signaled
//Inspect each active object added to the scheduler,
//in order of decreasing priority
//Call the event handler of the first which is active & completed
FOREVER
{
//Get the next active object in the priority queue
if (activeObject->IsActive())
&& (activeObject->iStatus!=KRequestPending)
{// Found an active object ready to handle an event
//Reset the iActive status to indicate it is not active
activeObject->iActive = EFalse;
//Call the active object’s event handler in a TRAP
TRAPD(r, activeObject->RunL()); if (KErrNone!=r)
{// event handler left, call RunError() on active object r = activeObject->RunError();
if (KErrNone!=r) // |
RunError() didn’t handle the error, |
|
Error(r); |
// |
call CActiveScheduler::Error() |
}
break; // Event handled. Break out of lookup loop & resume
130 |
ACTIVE OBJECTS UNDER THE HOOD |
}
} // End of FOREVER loop
}
If a single request has completed on the thread in the interim, the active scheduler performs lookup and calls the appropriate event handler on that active object. If more than one request has completed in that time, the active scheduler calls the event handler for the highest priority active object. It follows that, if multiple events are generated in close succession while another event is being handled, those events may not be handled in the sequence in which they occurred because the active object search list is ordered by priority to support responsive event-handling.
Normally, active object code should be designed so the priority does not matter, otherwise the system can become rather delicate and be thrown off balance by minor changes or additional active objects on the thread. However, to be responsive, say for user input, it is sometimes necessary to use a higher priority value. Long-running, incremental tasks, on the other hand, should have a lower priority than standard since they are designed to use idle processor time (as I’ll describe later in this chapter).
It’s important to understand that the priority value is only an indication of the order in which the active scheduler performs lookup and eventhandling when multiple events have completed. In contrast to the priority values of threads used by the kernel scheduler, it does not represent an ability to pre-empt other active objects. Thus, if you assign a particular active object a very high priority, and it completes while a lower-priority active object is handling an event, no pre-emption occurs. The RunL() of the lower-priority object runs to completion, regardless of the fact that it is ”holding up” the handler for the higher-priority object.
On Symbian OS, you cannot use active object priorities to achieve a guaranteed response time; for this you must use the pre-emptive scheduling associated with threads1, which is described in Chapter 10.
If you have a large number of active objects in a single thread which complete often, they ”compete” for their event handler to be run by the active scheduler. If some of the active objects have high priorities and receive frequent completion events, those with lower priorities wait indefinitely until the active scheduler can call their RunL() methods. In effect, it’s possible to ”hang” lower-priority active objects by adding them to an active scheduler upon which a number of high-priority active objects are completing.
1 The new hard real-time kernel in Symbian OS 8.0 can commit to a particular response time. On earlier versions of Symbian OS, the kernel has soft real-time capabilities and cannot make such guarantees.