- •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
RESPONSIBILITIES OF AN ACTIVE OBJECT |
131 |
An active object’s priority is only an indication of the order in which the active scheduler performs lookup and event-handling. It does not reflect an ability to pre-empt other active objects.
9.2 Responsibilities of an Active Object
Figure 9.1 illustrates the roles and actions of the active scheduler, active object and the asynchronous service provider. It extends Figure 8.1.
Executable |
Active Object |
Active Scheduler |
Asynchronous Service |
|
Provider |
||||
|
|
|
Create and Install the
Active Scheduler
Create Active Object (with appropriate priority) and add it to the Active Scheduler
Issue a request to the |
|
|
Make a request to the Asynchronous |
|
Sets iStatus=KRequestPending |
|||
Active Object |
|
|
Service Provider, passing in iStatus |
|
|
and starts the service |
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Call SetActive() |
|
|
|
|
|
Start the Active |
|
|
|
|
|
|
Wait Loop |
|
Scheduler if it is not |
CActiveScheduler::Start() |
|
|
|
|
|||
already started |
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Service completes and uses |
|
|
|
RunL() handles the |
Calls RunL() on the Active |
RequestComplete() to |
|||
|
|
|
completed event |
notify the Active Scheduler |
||||
|
|
|
Object with |
|
||||
|
|
|
and resubmits |
|
(by signalling its thread |
|||
|
|
|
iStatus!=KRequestPen |
|||||
|
|
|
another request or |
semaphore) and to post a |
||||
|
|
|
ding and iActive=ETrue |
|||||
|
|
|
stops the Active |
completion result |
||||
|
|
|
|
|
|
|||
|
|
|
Scheduler |
|
|
|
|
CActiveScheduler::Stop()
PROCESS OR
THREAD
BOUNDARY
Figure 9.1 Roles and actions of the active scheduler, an active object and an asynchronous service provider
132 |
ACTIVE OBJECTS UNDER THE HOOD |
It looks complex, but I’ll explain how it all fits together throughout this chapter and you’ll probably want to refer back to it later.
The following list summarizes the responsibilities of an active object:
•As I described in Chapter 8, the priority of an active object must be set on construction. The priority generally defaults to EPriorityStandard (=0, from class CActive) or EActivePriorityDefault (=0 if using the TActivePriority enumeration defined in coemain.h for use with application code). This is the standard priority for an active object and should be used unless there is a good reason to set its priority to some other value, for example to EActivePriorityWsEvents (=100) for handling user input responsively.
•An active object provides at least one method for clients to initiate requests to its encapsulated asynchronous service provider. The active object always passes its own iStatus object to the asynchronous function, so does not need to include a TRequestStatus reference among the parameters to the request issuer method unless it is acting as a secondary provider of asynchronous services.
•After submitting a request to an asynchronous service provider, the active object must call SetActive() upon itself. This sets the iActive flag, which indicates an outstanding request. This flag is used by the active scheduler upon receipt of an event and by the base class upon destruction, to determine whether the active object can be removed from the active scheduler.
•An active object should only submit one asynchronous request at a time. The active scheduler has no way of managing event-handling for multiple requests associated with one active object.
•An active object should pass its iStatus object to an asynchronous service function. It should not reuse that object until the asynchronous function has completed and been handled. The active scheduler inspects the TRequestStatus of each active object to determine its completion state and the event-handling code uses the value it contains to ascertain the completion result of the function.
•An active object must implement the pure virtual methods RunL() and DoCancel() declared in the CActive base class. Neither method should perform lengthy or complex processing, to avoid holding up event handling in the entire thread. This is particularly important in GUI applications where all user interface code runs in the same thread. If any single RunL() is particularly lengthy, the user interface will be unresponsive to input and will ”freeze” until that event handler has completed.
RESPONSIBILITIES OF AN ASYNCHRONOUS SERVICE PROVIDER |
133 |
•An active object must ensure that it is not awaiting completion of a pending request when it is about to be destroyed. As destruction removes it from the active scheduler, later completion will generate an event for which there is no associated active object. To prevent this, Cancel() should be called in the destructor of an active object. The destructor of the CActive base class checks that there is no outstanding request before removing the object from the active scheduler and raises an E32USER–CBASE 40 panic if there is, to highlight the programming error. The base class destructor cannot call Cancel() itself because that method calls the derived class implementation of DoCancel() – and, of course, C++ dictates that the derived class has already been destroyed by the time the base class destructor is called.
•Objects passed to the asynchronous service provider by the issuer methods must have a lifetime at least equal to the time taken to complete the request. This makes sense when you consider that the provider may use those objects until it is ready to complete the request, say if it is retrieving and writing data to a supplied buffer. This requirement means that parameters supplied to the provider should usually be allocated on the heap (very rarely, they may be on a stack frame that exists for the lifetime of the entire program). In general, parameters passed to the asynchronous service provider should belong to the active object, which is guaranteed to exist while the request is outstanding.
•If a leave can occur in RunL(), the class should override the default implementation of the virtual RunError() method to handle it. RunError() was added to CActive in Symbian OS v6.0 to handle any leaves that occur in the RunL() event handler. If a leave occurs, the active scheduler calls the RunError() method of the active object, passing in the leave code. RunError() should return KErrNone to indicate that it has handled the leave, say by cleaning up or resetting the state of the active object. The default implementation, CActive::RunError(), does not handle leaves and indicates this by simply returning the leave code passed in.
9.3Responsibilities of an Asynchronous Service Provider
An asynchronous service provider has the following responsibilities:
•Before beginning to process each request, the provider must set the incoming TRequestStatus value to KRequestPending to indicate to the active scheduler that a request is ongoing.
134 |
ACTIVE OBJECTS UNDER THE HOOD |
•When the request is complete, the provider must set the TRequestStatus value to a result code other than KRequestPending by calling the appropriate RequestComplete() method from the
RThread or User class.
•The asynchronous service provider must only call RequestComplete() once for each request. This method generates an event in the requesting thread to notify it of completion. Multiple completion events on a single active object result in a stray signal panic. Completion may occur normally, because of an error condition or because the client has cancelled an outstanding request. If the client calls Cancel() on a request after it has completed, the asynchronous service provider must not complete it again and should simply ignore the cancellation request. This is discussed further in Sections 9.8 and 9.9.
•The provider must supply a corresponding cancellation method for each asynchronous request; this should complete an outstanding request immediately, posting KErrCancel into the TRequestStatus object associated with the initial request.
9.4Responsibilities of the Active Scheduler
The active scheduler has the following responsibilities:
•Suspending the thread by a call to User::WaitForAnyRequest(). When an event is generated, it resumes the thread and inspects the list of active objects to determine which has issued the request that has completed and should be handled.
•Ensuring that each request is handled only once. The active scheduler should reset the iActive flag of an active object before calling its handler method. This allows the active object to issue a new request from its RunL() event handler, which results in SetActive() being called (which would panic if the active object was still marked active from the previous request).
•Placing a TRAP harness around RunL() calls to catch any leaves occurring in the event-handling code. If the RunL() call leaves, the active scheduler calls RunError() on the active object initially. If the leave is not handled there, it passes the leave code to CActiveScheduler::Error(), described in more detail shortly.
•Raising a panic (E32USER–CBASE 46) if it receives a ”stray signal”. This occurs when the request semaphore has been notified of an event, but the active scheduler cannot find a ”suitable” active object