- •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
LONG-RUNNING TASKS |
143 |
In effect, CStateMachine maintains a series of outstanding requests to the service provider in RunL(), rather than making a single call. This example is quite straightforward because there are only three active states, but the code logic here can potentially be far more complex. The DoCancel() method of CStateMachine must also have some state-related logic so that the correct method on CServiceProvider is cancelled. The states and transitions are illustrated in Figure 9.2.
Send TranslatedData()
Call CServiceProvider::GetData()
1. EIdle |
2. EGet |
Cancel()or
RunError()
Cancel(),
RunError() or
RunL() calls
normal completion
RunError() CServiceProvider::
TranslateData()
(self completes)
4. ESend |
3. ETranslate |
RunL() calls
CServiceProvider::SendData()
Figure 9.2 Internal states of CStateMachine
In this example the service provider functions called by the state machine are a mixture of synchronous (TranslateData()) and asynchronous (GetData() and SendData()) functions. The synchronous method uses self-completion to simulate an asynchronous completion event, which is discussed further in the following section.
9.11 Long-Running Tasks
Besides encapsulating asynchronous service providers, active objects can also be used to implement long-running tasks which would otherwise need to run in a lower-priority background thread.
To be suitable, the task must be divisible into multiple short increments, for example, preparing data for printing, performing background recalculations and compacting the database. The increments are performed in the event handler of the active object. For this reason, they must be short enough for event handling in the thread to continue to be responsive, because RunL() cannot be pre-empted once it is running.
The active object should be assigned a low priority such as
CActive::TPriority::EPriorityIdle (=-100), which determines that a task increment only runs when there are no other events to
144 |
ACTIVE OBJECTS UNDER THE HOOD |
handle, i.e. in idle time. If the task consists of a number of different steps, the active object must track the progress as series of states, implementing it using a state machine as described above.
The active object drives the task by generating its own events to invoke its event handler. Instead of calling an asynchronous service provider, it completes itself by calling User::RequestComplete() on its own iStatus object and calls SetActive() on itself so the active scheduler calls its event handler. In this way it continues to resubmit requests until the entire task is complete. A typical example is shown in the sample code, where I’ve shown all the relevant methods in the class declarations but only the implementations relevant to this discussion. I’ve also omitted error handling for clarity:
// This class has no dependence on the active object framework class CLongRunningCalculation : public CBase
{
public:
static CLongRunningCalculation* NewL();
TBool StartTask(); |
// Initialization before starting the task |
TBool DoTaskStep(); |
// Performs a short task step |
void EndTask(); |
// Destroys intermediate data |
... |
|
}; |
|
TBool CLongRunningCalculation::DoTaskStep() {// Do a short task step, returning
//ETrue if there is more of the task to do
//EFalse if the task is complete
... // Omitted
}
_LIT(KExPanic, "CActiveExample");
class CBackgroundRecalc : public CActive
{
public:
... // NewL(), destructor etc are omitted for clarity public:
void PerformRecalculation(TRequestStatus& aStatus); protected:
CBackgroundRecalc(); void ConstructL(); void Complete(); virtual void RunL();
virtual void DoCancel(); private:
CLongRunningCalculation* iCalc;
TBool iMoreToDo;
TRequestStatus* iCallerStatus; // To notify caller on completion };
CBackgroundRecalc::CBackgroundRecalc()
: CActive(EPriorityIdle) // Low priority task { CActiveScheduler::Add(this); }
LONG-RUNNING TASKS |
145 |
//Issues a request to initiate a lengthy task
void CBackgroundRecalc::PerformRecalculation(TRequestStatus& aStatus)
{
iCallerStatus = &aStatus; *iCallerStatus = KRequestPending;
__ASSERT_ALWAYS(!IsActive(), User::Panic(KExPanic, KErrInUse)); iMoreToDo = iCalc->StartTask(); // iCalc initializes the task
Complete(); |
// Self-completion to generate an event |
} |
|
void CBackgroundRecalc::Complete()
{// Generates an event on itself by completing on iStatus TRequestStatus* status = &iStatus; User::RequestComplete(status, KErrNone);
SetActive();
}
// Performs the background task in increments void CBackgroundRecalc::RunL()
{// Resubmit request for next increment of the task or stop if (!iMoreToDo)
{// Allow iCalc to cleanup any intermediate data iCalc->EndTask();
// Notify the caller User::RequestComplete(iCallerStatus, iStatus.Int());
}
else
{// Submit another request and self-complete to generate event iMoreToDo = iCalc->DoTaskStep();
Complete();
}
}
void CBackgroundRecalc::DoCancel()
{// Give iCalc a chance to perform cleanup if (iCalc)
iCalc->EndTask();
if (iCallerStatus) // Notify the caller User::RequestComplete(iCallerStatus, KErrCancel);
}
If you are designing an API for a long-running task, to make it useful with this pattern, it is a good idea to provide methods which split the task into increments, as in the example above. StartTask(), DoTaskStep() and EndTask() perform small, discrete chunks of the task and can be called directly by the RunL() method of the low-priority active object. The API can then also be reused by code which implements long-running tasks differently, since it will have no dependence on the active object itself. Besides portability, distancing the active object model makes it more straightforward to focus on the implementation of the long-running task itself.
Of course, one disadvantage of this approach is that some tasks cannot be broken down into short steps. Another is that if you implement a
146 |
ACTIVE OBJECTS UNDER THE HOOD |
number of low-priority active objects for long-running tasks in the same thread, you will need to work out how best to run them together and write the necessary low-priority scheduling code yourself.
The use of a background thread for long-running tasks is fairly straightforward. The code for the task can be written without worrying about yielding the CPU, since the kernel schedules it when no higher-priority thread needs to run, pre-empting it again when a more important thread needs access to system resources. However, as with all multi-threaded code, any shared resources must be protected against illegal concurrent access by synchronization objects and, on Symbian OS, the overhead of running multiple threads is significantly higher than that for multiple active objects running in a single thread. You should prefer low-priority active objects for long-running tasks, except for cases where the task cannot be split into convenient increments. The next chapter illustrates how to use a separate thread to perform a long-running task which is wrapped with code for an active object.
9.12Class CIdle
CIdle derives from CActive and is a useful class which wraps the active object basics such as implementing RunL() and DoCancel(). The wrapper allows you to focus solely on implementing the code to run the incremental task without worrying about the active object code.
class CIdle : public CActive
{
public:
IMPORT_C static CIdle* New(TInt aPriority);
IMPORT_C static CIdle* NewL(TInt aPriority);
IMPORT_C CIdle();
IMPORT_C void Start(TCallBack aCallBack); protected:
IMPORT_C CIdle(TInt aPriority);
IMPORT_C void RunL();
IMPORT_C void DoCancel(); protected:
TCallBack iCallBack;
};
The CIdle object should be created with a low or idle priority and the long-running task initiated by a call to Start(), passing a callback function to perform an increment of the task. The TCallback object encapsulates a pointer to a callback function which takes an argument of type TAny* and returns an integer. The callback function manages the task increments and can be a local or a static member function. It should keep track of the task progress, returning ETrue if further steps are
CLASS CIdle |
147 |
necessary and EFalse when it is complete. In much the same way as the incremental task shown above, the RunL() event handler which calls the TCallback object is only called during idle time. Furthermore, it will not be pre-empted while it is running. As long as the callback function indicates that further steps of the task are required, CIdle::RunL() resubmits requests by completing on its own iStatus object and calling
SetActive().
Here’s some example code for another background recalculation task class. In fact, I’ve slightly reworked the class from the example above to be driven by CIdle. You’ll notice that there’s no ”boilerplate” active object code required, unlike the code in the CBackgroundRecalc class, because CIdle provides that functionality.
class CLongRunningCalculation : public CBase
{
public:
static CLongRunningCalculation* NewLC(TRequestStatus& aStatus);
protected:
static TInt TaskStep(TAny* aLongCalc); // Callback function
protected: |
|
void StartTask(); |
// Initialization before starting the task |
TBool DoTaskStep(); |
// Does a step of the task |
void EndTask(); |
// Destroys intermediate data |
protected: |
|
CLongRunningCalculation(TRequestStatus& aStatus); private:
...
TBool iMoreToDo; // Flag tracks the progress of the task // To notify the caller when the calc is complete TRequestStatus* iCallerStatus;
};
CLongRunningCalculation* CLongRunningCalculation::NewLC(TRequestStatus& aStatus)
{
CLongRunningCalculation* me = new (ELeave)
CLongRunningCalculation(aStatus);
CleanupStack::PushL(me); // ... 2nd phase construction code omitted return (me);
}
CLongRunningCalculation::CLongRunningCalculation(TRequestStatus&
aStatus)
: iCallerStatus(&aStatus) {}
TBool CLongRunningCalculation::DoTaskStep()
{// Does a short task step, returning ETrue if it has more to do
... // Omitted
}
void CLongRunningCalculation::StartTask() {// Prepares the task
iMoreToDo = ETrue;
}
148 ACTIVE OBJECTS UNDER THE HOOD
// Error handling omitted
void CLongRunningCalculation::EndTask()
{// Performs cleanup when the task has completed ASSERT(!iMoreToDo);
...
User::RequestComplete(iCallerStatus, KErrNone);
}
TInt CLongRunningCalculation::TaskStep(TAny* aLongCalc)
{
ASSERT(aLongCalc);
CLongRunningCalculation* longCalc =
static_cast<CLongRunningCalculation*>(aLongCalc); if (!longCalc->iMoreToDo)
longCalc->EndTask(); else
longCalc->iMoreToDo = longCalc->DoTaskStep();
return (longCalc->iMoreToDo);
}
Code which uses the idle object will look something like the following example, which creates the CIdle object and a CLongRunningCalculation object that drives the task:
CIdle* idle = CIdle::NewL(EPriorityIdle);
CleanupStack::PushL(idle);
// Create the object that runs the task, passing in a TRequestStatus& CLongRunningCalculation* longCalc =
CLongRunningCalculation::NewLC(iStatus);
TCallBack callback(CLongRunningCalculation::TaskStep, longCalc); idle->Start(callback);
... // Completion of the task completes iStatus
9.13Class CPeriodic
CPeriodic is another useful CActive-derived class for running incremental task steps. It uses a timer object to generate regular timer events, handling a single step of the task for each timer event. CPeriodic is useful for performing regular task steps such as flashing a cursor or for controlling time-outs.
Like CIdle, CPeriodic is initialized with a priority value and the task is initiated by a call to Start(), passing settings for the timer as well as a callback to perform increments of the task. When the timer period elapses, the CPeriodic object generates an event that is detected by the active scheduler. When there are no active objects of higher priority requiring event handling, the active scheduler calls the RunL() event