- •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
WORKING WITH ACTIVE OBJECTS |
115 |
8.3 Working with Active Objects
Let’s move on now to consider in more detail how active objects work, and how to use them. A typical Symbian OS application or server consists of a single event-handling thread running a scheduler (the ”active scheduler”) which coordinates one or more active objects. Each active object requests an asynchronous service and handles the resulting completion event some time after the request. It also provides a way to cancel an outstanding request and may provide error handling for exceptional conditions.
An active object class must derive from class CActive, which is defined in e32base.h (shown here with only the relevant methods, for clarity). The next chapter discusses details of the base class further.
class CActive : public CBase
{
public:
enum TPriority
{
EPriorityIdle=-100,
EPriorityLow=-20,
EPriorityStandard=0,
EPriorityUserInput=10,
EPriorityHigh=20,
};
public:
IMPORT_C CActive(); IMPORT_C void Cancel();
...
IMPORT_C void SetPriority(TInt aPriority); inline TBool IsActive() const;
...
protected:
IMPORT_C CActive(TInt aPriority); IMPORT_C void SetActive(); virtual void DoCancel() =0; virtual void RunL() =0;
IMPORT_C virtual TInt RunError(TInt aError); public:
TRequestStatus iStatus;
...
};
Construction
Like threads, active objects have a priority value to determine how they are scheduled. Classes deriving from CActive must call the protected constructor of the base class, passing in a parameter to set the priority of the active object.
When the asynchronous service associated with the active object completes, it generates an event. The active scheduler detects events, determines which active object is associated with each event, and calls
116 |
EVENT-DRIVEN MULTITASKING USING ACTIVE OBJECTS |
the appropriate active object to handle the event. I’ll describe this in more detail in the later section on event handling. While an active object is handling an event, it cannot be pre-empted3 until the event handler function has returned back to the active scheduler.
It is quite possible that a number of events may complete before control returns to the scheduler. The scheduler must resolve which active object gets to run next; it does this by ordering the active objects using their priority values. If multiple events have occurred before control returns to the scheduler, they are handled sequentially in order of the highest active object priority, rather than in order of completion. Otherwise, an event of low priority that completed just before a more important one would supplant the higher-priority event for an undefined period, depending on how much code it executed to run to completion.
A set of priority values are defined in the TPriority enumeration of class CActive. For the purposes of this chapter, and in general, you should use the priority value EPriorityStandard (=0) unless you have good reason to do otherwise. The next chapter discusses the factors you should consider when setting the priority of your active objects on construction or by making additional calls to CActive::SetPriority().
As part of construction, the active object code should call a static function on the active scheduler, CActiveScheduler::Add(). This will add the object to a list maintained by the active scheduler of event-handling active objects on that thread.
An active object typically owns an object to which it issues requests that complete asynchronously, generating an event, such as a timer object of type RTimer. This object is generally known as an asynchronous service provider and it may need to be initialized as part of construction. Of course, if the initialization can fail, you should perform it as part of the second-phase construction, as described in Chapter 4.
Submitting Requests
An active object class supplies public methods for callers to initiate requests. These will submit requests to the asynchronous service provider associated with the active object, using a well-established pattern, as follows:
1.Request methods should check that there is no request already submitted before attempting to submit another. Each active object
3 While this is true under most circumstances, it is possible to nest a separate active scheduler within the event handler of an active object, and use the nested active scheduler to receive other active objects’ events. This is used in Uikon for modal ”waiting” dialogs, as discussed further in the next chapter. However, this technique can cause complications and should be used with caution. For the purposes of discussion in this chapter, you should simply consider that all active object event handling is non-preemptive.
WORKING WITH ACTIVE OBJECTS |
117 |
can only ever have one outstanding request, for reasons I’ll discuss further in the next chapter. Depending on the implementation, the code may:
•panic if a request has already been issued, if this scenario could only occur because of a programming error
•refuse to submit another request (if it is legitimate to attempt to make more than one request)
•cancel the outstanding request and submit the new one.
2.The active object should then issue the request to the service provider, passing in its iStatus member variable as the TRequestStatus& parameter.
3.If the request is submitted successfully, the request method then calls the SetActive() method of the CActive base class, to indicate to the active scheduler that a request has been submitted and is currently outstanding. This call is not made until after the request has been submitted.
Event Handling
Each active object class must implement the pure virtual RunL() member method of the CActive base class. When a completion event occurs from the associated asynchronous service provider and the active scheduler selects the active object to handle the event, it calls RunL() on the active object.4 The function has a slightly misleading name because the asynchronous function has already run. Perhaps a clearer description would be HandleEventL() or HandleCompletionL().
Typical implementations of RunL() determine whether the asynchronous request succeeded by inspecting the completion code in the TRequestStatus object (iStatus) of the active object, a 32-bit integer value. Depending on the result, RunL() usually either issues another request or notifies other objects in the system of the event’s completion; however, the degree of complexity of code in RunL() can vary considerably. Whatever it does, once RunL() is executing, it cannot be pre-empted by other active objects’ event handlers. For this reason, the code should complete as quickly as possible so that other events can be handled without delay.
Figure 8.1 illustrates the basic sequence of actions performed when an active object submits a request to an asynchronous service provider that later completes, generating an event which is handled by RunL().
4 Advanced Windows programmers will recognize the pattern of a message loop and message dispatch which drives a Win32 application. On Symbian OS, the active scheduler takes the place of the Windows message loop and the RunL() function of an active object acts as a message handler.
118 |
EVENT-DRIVEN MULTITASKING USING ACTIVE OBJECTS |
Active Object
(1) Issues request passing iStatus
(3) Calls SetActive() on itself
(5) Active Scheduler calls RunL() on the active object to handle the event
RunL() resubmits another request or stops the Active Scheduler
RunL() cannot be preempted
Asynchronous Service
Provider
(2) Sets iStatus=KRequestPending and starts the service
(4) Service completes. Service provider uses
RequestComplete() to notify the Active Scheduler and post a completion result
PROCESS OR
THREAD
BOUNDARY
Figure 8.1 Process of submitting a request to an asynchronous service provider, which generates an event on completion
Each active object class must implement the pure virtual RunL() member method of the CActive base class to handle completion events.
Cancellation
The active object must be able to cancel any outstanding asynchronous requests it has issued, for example, if the application thread in which it
WORKING WITH ACTIVE OBJECTS |
119 |
is running is about to terminate. The CActive base class implements a Cancel() method which calls the pure virtual DoCancel() method (which the derived active object class must implement) and waits for the request’s early completion. Any implementation of DoCancel() should call the appropriate cancellation method on the asynchronous service provider. DoCancel() can also include other processing, but should not leave or allocate resources and should not carry out any lengthy operations. It’s a good rule to restrict the method to cancellation and any necessary cleanup associated with the request, rather than implementing any sophisticated functionality. This is because a destructor should call Cancel(), as described in later, and may have already cleaned up resources that DoCancel() may require.
It isn’t necessary to check whether a request is outstanding for the active object before calling Cancel(), because it is safe to do so even if it isn’t currently active. The next chapter discusses in detail the semantics of canceling active objects.
Error Handling
From Symbian OS v6.0 onwards, the CActive class provides a virtual RunError() method which the active scheduler calls if a leave occurs in the RunL() method of the active object. The method takes the leave code as a parameter and returns an error code to indicate whether the leave has been handled. The default implementation does not handle the leave and simply returns the leave code passed to it. If the active object can handle any leaves occurring in RunL(), it should do so by overriding the default implementation and returning KErrNone to indicate that the leave has been handled.
If RunError() returns a value other than KErrNone, indicating that the leave has yet to be dealt with, the active scheduler calls its Error() function to handle it. The active scheduler does not have any contextual information about the active object with which to perform error handling. For this reason, it is generally preferable to manage error recovery within the RunError() method of the associated active object, where more context is usually available.
Destruction
The destructor of a CActive-derived class should always call Cancel() to terminate any outstanding requests as part of cleanup code. This should ideally be done before any other resources owned by the active object are destroyed, in case they are used by the service provider or the DoCancel() method. The destructor code should, as usual, free all