Issott_Common Design Patterns for Symbian OS-The Foundations of Smartphone Software_0470516356 (779879), страница 37
Текст из файла (страница 37)
The server intercepts this message, instead ofdispatching it to the server session, to close and destroy the serversession. If this is the last session closed then a transient server alsoun-registers and destroys itself. In effect, this is an application of LazyDe-allocation (see page 73) where the server is the resource.Note that the Symbian OS client–server framework ensures that theserver is kept notified of which clients are connected to the server atall times. This is true even if the client thread is terminated, such asif it panics, as an EDisconnect message is still sent to the server.This is often a key distinction between this pattern and other ways ofimplementing IPC.ImplementationThis pattern is codified into the client–server architectural framework ofSymbian OS. You will find a lot of information describing the framework192PROVIDING SERVICESin books such as [Babin, 2007], [Harrison and Shackman, 2007] and[Heath, 2006] as well as in the Symbian Developer Library.
For thecomplete implementation of this pattern, please see those sources ofinformation.Here we provide code snippets to illustrate how to implement a serverand a client session based on the existing Symbian OS client–serverframework. We also describe code showing how a typical client interactswith the server through its client session.Important Note: The code snippets are provided for illustration purposes only. To keep the information manageable we don’t always includethe typical house-keeping code needed to avoid resource leaks andso on.Implementing the Server Side• Creating and starting the serverThe system loads the server’s code16 when it is required for thefirst time.
It passes control to E32Main() which is responsible forconstructing and running the server object. Note that, for clarity, theimplementation below shows a non-transient server:// CMyServerinline CMyServer::CMyServer():CServer2(CActive::EPriorityStandard, ESharableSessions){}CServer2* CMyServer::NewLC(){CMyServer* self = new(ELeave) CMyServer;CleanupStack::PushL(self);self->ConstructL();return self;}// 2nd phase construction - ensure the server object is runningvoid CMyServer::ConstructL(){StartL(KMyServerName);}// CMyServerSessioninline CMyServerSession::CMyServerSession(){}inline CMyServer& CMyServerSession::Server(){return *static_cast<CMyServer*>(const_cast<CServer2*>(CMyServerSession2::Server()));}16 Usuallypackaged within an EXE file.CLIENT–SERVER193// Perform all server initialization, in particular creation of the// scheduler and server before starting the schedulerstatic void RunServerL(){// Naming the thread after the server helps to debug panicsUser::LeaveIfError(RThread::RenameMe(KMyServerName));// Create and install the server active schedulerCActiveScheduler* s = new(ELeave) CActiveScheduler;CleanupStack::PushL(s);CActiveScheduler::Install(s);// Create the server (leave it on the cleanup stack)CMyServer::NewLC();// Initialization complete, now signal the clientRProcess::Rendezvous(KErrNone);// Ready to runCActiveScheduler::Start(); // Doesn’t return until the server dies// Clean up the server and schedulerCleanupStack::PopAndDestroy(2);}// Entry point to server processTInt E32Main(){__UHEAP_MARK;CTrapCleanup* cleanup = CTrapCleanup::New();TInt r = KErrNoMemory;if (cleanup){TRAP(r, RunServerL());delete cleanup;}__UHEAP_MARKEND;return r;}• Responding to a client connection requestA client’s connection request is intercepted by the constructed serverin its base implementation, CServer2.
At some point, the virtualfunction NewSessionL() is called on the sub-class to constructa specific server session object to manage the connection to theclient.// Create a new client session.CMyServerSession2* CMyServer::NewSessionL(const TVersion&,const RMessage2&) const{// Could also check that the version passed from the client-side// library is compatible with the server’s own version.194PROVIDING SERVICESreturn new(ELeave) CMyServerSession();}// Called by the framework when a new session is createdvoid CMyServer::AddSession(){++iSessionCount;}• The server message dispatchThe server dispatches client messages to the session to un-marshaland handle. In the example below, the session calls specific methodson the server corresponding to the request.
By going through thesession first, the framework allows the developer to do any clientspecific pre-processing of the message in the session before it callsthe server.// Handle a client requestvoid CMyServerSession::ServiceL(const RMessage2& aMessage){switch (aMessage.Function()){case EGetValue:GetValueL(aMessage);break;case EDoActivity:iActivityMsg = aMessage;Server().DoActivity(*this);break;case EObserve:iNotifyMsg = aMessage;Server().ObserveL(*this);break;case ECancelActivity:Server().CancelActivity();aMessage.Complete(KErrNone);break;default:aMessage.Panic(KServerPanic, EPanicIllegalFunction);break;}}• Server error handlingIn servicing the message, the server session relies heavily on EscalateErrors (see page 32) this is partly because it reduces the amount oferror-handling code but mainly because errors are escalated to a pointat which there is sufficient context to handle them effectively.
Thefirst opportunity to resolve any errors is in this pattern’s equivalent ofRunError() which is also optional:CLIENT–SERVER195void CMySession::ServiceError(const RMessage2& aMessage, TInt aError)){if (aError == KErrBadDescriptor){// A bad descriptor error implies a badly programmed client, so// panic itaMessage.Panic(KServerPanic, EPanicBadDescriptor);}// Otherwise escalate the error to the client-server frameworkCSession2::ServiceError(aMessage, aError);}Normally an error can only be escalated within a single thread,however CSession2::ServiceError() will handle errors thatreach it by completing the message currently being serviced withthe error. This allows you to escalate an error back into the client’sthread17 where it is often much more appropriate to handle the error.An alternative error-handling strategy is to use Fail Fast (see page17) to panic programming faults.
Just like Leave, a panic normallyaffects just the local thread. However, the client–server frameworkgives you the option of panicking a client if you’ve detected that it isat fault. This is done by calling Panic() on the RMessage2 objectyou’re currently servicing; the client the message came from willbe terminated. In the ServiceL() implementation, if an unknownmessage type is received then someone must be sending messagesdirectly without using the client session which, for released code, isprobably a sign of an attempted security attack.• Providing a simple request serviceIn the server message dispatch code snippet above you’ll notice howCMyServerSession interprets the EGetValue message into a callon CMyServer:void CMyServerSession::GetValueL(RMessage2& aMessage){TInt results = Server().GetValue();TPtr ptr((TUint8*)&results, sizeof(results), sizeof(results));aMessage.WriteL(0, ptr);aMessage.Complete(KErrNone);}TInt CMyServer::GetValue(){return iValue;}17 In the client session, you need to Leave again if you wish to escalate the error up theclient’s call stack.196PROVIDING SERVICES• Observing changes to the server valueThe example below shows a server that only allows one client ata time to observe it.
Here the observer is the server session. Thisabstraction is done to make the server code more portable – it is ableto collaborate with clients in different configurations, for examplein a unit test environment. For simplicity, the notification style heredoesn’t include the new value so clients would have to make anRMyClientSession::GetValueL() call to retrieve it if needed.void CMyServer::ObserveL(MObserver& aObserver){iObserver = &aObserver; // Supports just one observer, for the sake// of brevity}TInt CMyServer::ChangeValue(){++iValue;if (iObserver){iObserver->NotifyChange(KErrNone);iObserver = NULL;}return iValue;}void CMyServerSession::NotifyChange(TInt aError){iNotifyMsg.Complete(aError);}• Initiating an asynchronous serviceCMyServerSession calls CMyServer::DoActivityL() in response to the EDoActivityL command.
The server delegates theactivity to a servant active object, CMyServant. CMyServant isdesigned as an Asynchronous Controller (see page 148) to breakdown its task into smaller steps so it can run in the server’s thread.void CMyServer::DoActivityL(MActivityEventMixin& aActivityEventMixin){// This server is only designed to have one of this// activity running at a time;CancelActivity();iServant = CMyServant::NewL(aMixin);iActivityEventMixin = &aActivityEventMixin;iValue = 0; // Reset the value at the start of the activity}CLIENT–SERVER197• Performing the asynchronous activity and notifying changeThe servant performs the delegated activity in small steps.
In thisexample, each step calls the server to change its value and to notifyclients of the change.const TInt KMaxCount = 100000;// from CActivevoid CMyServant::RunL(){NextStep();}void CMyServant::NextStep(){// Do some activity that changes the server valueTInt current_value = iServer.ChangeValue();if (current_value == KMaxCount){iServer.ActivityComplete();}else{// Activate self to be run againSelfComplete();}}• Completing the activityWhen the servant has completed its work, it calls CMyServer::ActivityComplete(). The server then notifies the server sessionof the completion.void CMyServer::ActivityComplete(){// The servant has completed its work so dismiss itdelete iServant;iServant = NULL;if (iActivityEventMixin){// Inform the client that the work is completediActivityEventMixin->ActivityComplete(KErrNone);iActivityEventMixin = NULL;}if (iObserver){// There’s going to be no more changes so// notify the observer to ensure it doesn’t wait in vain198PROVIDING SERVICESiObserver->NotifyChange(KErrNone);iObserver = NULL;}}void CMyServerSession::ActivityComplete(TInt aError){iActivityMsg.Complete(aError);}• Canceling the activityIf the activity is canceled early then the server needs to notify theserver session of the completion:void CMyServer::CancelActivity(){if (iServant){// Dismiss the servantdelete iServant;iServant = NULL;}if (iActivityEventMixin){// Inform our client that the work is completediActivityEventMixin->ActivityComplete(KErrCancel);iActivityEventMixin = NULL;}if (iObserver){// There’s going to be no more changes so// notify the observer to ensure it doesn’t wait in vainiObserver->NotifyChange(KErrCancel);iObserver = NULL;}}Implementing a Client Session – RMyClientSessionThe client session should be packaged into a client-side DLL separate fromserver.exe.