- •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
120 |
EVENT-DRIVEN MULTITASKING USING ACTIVE OBJECTS |
resources owned by the object, including any handle to the asynchronous service provider.
The CActive base class destructor is virtual and its implementation checks that the active object is not currently active. It panics if any request is outstanding, that is, if Cancel() has not been called. This catches any programming errors which could lead to the situation where a request completes after the active object to handle it has been destroyed. This would otherwise result in a ”stray signal”, described further in Chapter 9, where the active scheduler cannot locate an active object to handle the event.
Having verified that the active object has no issued requests outstanding, the CActive destructor removes the active object from the active scheduler.
The destructor of a CActive-derived class should always call Cancel() to terminate any outstanding requests before cleanup proceeds.
8.4 Example Code
The example below illustrates the use of an active object class to wrap an asynchronous service, in this case a timer provided by the RTimer service. In fact, Symbian OS already supplies an abstract active object class, CTimer, which wraps RTimer and can be derived from, specifying the action required when the timer expires. However, I’ve created a new class, CExampleTimer, because it’s a straightforward way of illustrating active objects. Figure 8.2 illustrates the classes involved and their relationship with the active scheduler.
When the timer expires, the RunL() event handler checks the active object’s iStatus result and leaves if it contains a value other than KErrNone so the RunError() method can handle the problem. In this case, the error handling is very simple: the error returned from the request is logged to file. This could have been performed in the RunL() method, but I’ve separated it into RunError() to illustrate how to use the active object framework to split error handling from the main logic of the event handler. If no error occurred, the RunL() event handler logs the timer completion to debug output using RDebug::Print() (described in Chapter 17) and resubmits the timer request with the stored interval value. In effect, once the timer request has started, it continues to expire and be resubmitted until it is stopped by a call to the Cancel() method on the active object.
|
|
EXAMPLE CODE |
121 |
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
CActive |
|
|
|
|
|
|
CActiveScheduler |
|
|
|
|
|
|
|
|
|
|
|
|
|
RunL() |
|
|
n |
1 |
|
Add() |
|
|||
DoCancel() |
|
|
|
|
||||||
|
|
|
|
|
|
|||||
RunError() |
|
|
|
|
|
... |
|
|
||
... |
|
|
|
|
|
|
|
|
|
|
iActive |
|
|
|
|
|
|
|
|
|
|
iStatus |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CExampleTimer
NewL() After RunL() DoCancel()
RunError()
...
iTimer
RTimer
Figure 8.2 CExampleTimer and its relationship with RTimer, CActive and CActiveScheduler
_LIT(KExampleTimerPanic, "CExampleTimer");
class CExampleTimer : public CActive
{
public:
CExampleTimer();
static CExampleTimer* NewL();
void After(TTimeIntervalMicroSeconds32& aInterval);
protected: |
|
CExampleTimer(); |
|
void ConstructL(); |
|
protected: |
|
virtual void RunL(); |
// Inherited from CActive |
virtual void DoCancel();
virtual TInt RunError(TInt aError); private:
RTimer iTimer; TTimeIntervalMicroSeconds32 iInterval; };
CExampleTimer::CExampleTimer()
: CActive(EPriorityStandard) { CActiveScheduler::Add(this); }
void CExampleTimer::ConstructL()
122 |
EVENT-DRIVEN MULTITASKING USING ACTIVE OBJECTS |
{// Create the asynchronous service provider User::LeaveIfError(iTimer.CreateLocal());
}
CExampleTimer* CExampleTimer::NewL()
{...} // Code omitted for clarity
CExampleTimer:: CExampleTimer()
{
Cancel();
iTimer.Close();
}
void CExampleTimer::After(TTimeIntervalMicroSeconds32& aInterval) {// Only allow one timer request to be submitted at a time
// Caller must call Cancel() before submitting another __ASSERT_ALWAYS(!IsActive(), User::Panic(KExampleTimerPanic,
KErrInUse)); iInterval = aInterval;
iTimer.After(iStatus,aInterval); // Set the RTimer SetActive(); // Mark this object active
}
void CExampleTimer::RunL()
{// If an error occurred (admittedly unlikely) // deal with the problem in RunError()
User::LeaveIfError(iStatus.Int());
// Otherwise, log the timer completion and resubmit the timer _LIT(KTimerExpired, "Timer Expired\n"); RDebug::Print(KTimerExpired); // See Chapter 17 for more details iTimer.After(iStatus, iInterval);
SetActive();
}
void CExampleTimer::DoCancel() {// Cancel the timer iTimer.Cancel();
}
TInt CExampleTimer::RunError(TInt aError)
{// Called if RunL() leaves, aError contains the leave code _LIT(KErrorLog, "Timer error %d"); RDebug::Print(KErrorLog, aError); // Logs the error
return (KErrNone); |
// Error has been handled |
} |
|
The example is simplistic but it demonstrates the use of an active object to wrap an asynchronous function, in this case, timer completion. Here’s how a client of class CExampleTimer can expect to use the active object, which it stores as a member variable called iExampleTimer, illustrating the transparency of active object classes from a client’s perspective:
// Class CClient has a member variable CExampleTimer* iExampleTimer
void CClient::StartRepeatingTimerL()
{
iExampleTimer = CExampleTimer::NewL();
APPLICATION CODE AND ACTIVE OBJECTS |
123 |
iExampleTimer->After(1000000);
}
void CClient::StopRepeatingTimer()
{
iExampleTimer->Cancel(); delete iExampleTimer;
iExampleTimer = NULL; // Prevents re-use or double deletion
}
8.5 Threads Without an Active Scheduler
Most threads running on Symbian OS have an active scheduler, which is usually created implicitly by a framework (e.g. CONE for the GUI framework). However, if you are implementing a server, you have to create and start an active scheduler explicitly before you can use active objects. Likewise, console-based test code may not use active objects directly itself, but must create an active scheduler in its main thread if it depends on components which do use active objects.
There are a few threads in Symbian OS which intentionally do not have an active scheduler and thus cannot use active objects or components that use them:
•The Java implementation does not support an active scheduler and native Java methods may not use active objects. It is permissible to make calls in code to C++ servers which do use them, since these run in a separate thread which has a supporting active scheduler.
•The C Standard Library, STDLIB, thread has no active scheduler and thus standard library code cannot use active objects. Functions provided by the Standard Library may however be used in active object code, for example in an initialization or a RunL() method. The functions should be synchronous and return quickly, as required by all active object implementations.
•OPL does not provide an active scheduler and C++ extensions to OPL (OPXs) must not use active objects or any component which uses them.
8.6Application Code and Active Objects
The active object model is very easy to use without needing a full understanding of how it works. In this chapter, I’ve described how to handle events resulting from the completion of asynchronous functions on Symbian OS. This involves defining CActive-derived classes, and providing