Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

SERVER STARTUP CODE

203

// Descriptorizes ’this’ to pass it from client to server inline TPtrC TServerStart::AsCommand() const

{return TPtrC(reinterpret_cast<const TText*>(this), sizeof(TServerStart)/sizeof(TText));}

// Called server-side to ’reconstitute’ an object of this type TInt TServerStart::GetCommand()

{

RProcess serverProcess;

if (serverProcess.CommandLineLength()! = sizeof(TServerStart)/sizeof(TText))

return (KErrGeneral);

TPtr ptr(reinterpret_cast<TText*>(this),0, sizeof(TServerStart)/sizeof(TText));

serverProcess.CommandLine(ptr); return (KErrNone);

}

//Called server-side to notify the client that server initialization

//completed successfully

void TServerStart::SignalL()

{

RThread client;

User::LeaveIfError(client.Open(iId));

client.RequestComplete(iStatus, KErrNone);

client.Close();

}

A TServerStart object encapsulates the client’s TThreadId and a pointer to a TRequestStatus object. For hardware builds, the object is passed between the client and server processes by wrapping it in a descriptor, using the AsCommand() method and passing it as a command line argument. Server-side, a TServerStart object can retrieve these values using GetCommand(), which reads the command line with which the process was created. On the emulator, the TServerStart object is passed as a parameter to the thread function. The server uses TServerStart::SignalL() to notify the client that initialization has completed successfully.

The code to start a server is quite complex because it differs depending on whether the server will run on a phone or the emulator. This complexity has been removed from Symbian OS v8.0 because Symbian OS process emulation on Win32 has been improved (see Chapter 10 for more details).

12.4 Server Startup Code

Having considered the client-side code to start the server, I will now move on to discuss the server-side startup code:

204 THE CLIENT–SERVER FRAMEWORK IN PRACTICE

// Initialize and run the server

static void RunTheServerL(TServerStart& aStart)

{// First create and install the active scheduler CActiveScheduler* scheduler = new (ELeave) CActiveScheduler; CleanupStack::PushL(scheduler); CActiveScheduler::Install(scheduler);

CHerculeanServer::NewLC();// creates the server

User::LeaveIfError(RThread().Rename(KServerName));

aStart.SignalL();// Signal the client that initialization is complete

CActiveScheduler::Start();// Enter the wait loop // Exited – cleanup the server and scheduler CleanupStack::PopAndDestroy(2, scheduler);

}

// Main entry-point for the server thread/process static TInt RunTheServer(TServerStart& aStart)

{

CTrapCleanup* cleanup=CTrapCleanup::New();

TInt r=KErrNoMemory;

if (cleanup)

{

TRAP(r,RunTheServerL(aStart)); delete cleanup;

}

return (r);

}

#ifdef __WINS__ // Different startup code for emulator and hardware

// Thread entry-point function

static TInt ThreadFunction(TAny* aParameters)

{// The TServerStart object is passed as the thread parameter return RunTheServer(*static_cast<TServerStart*>(aParameters));

}

// WINS DLL entry-point IMPORT_C TInt WinsMain(); EXPORT_C TInt WinsMain()

{// Returns the real thread function cast to TInt return reinterpret_cast<TInt>(&ThreadFunction);

}

TInt E32Dll(TDllReason)

{return (KErrNone);}

#else // Phone hardware // Process entry-point TInt E32Main()

{// Reads the startup parameters and runs the server TServerStart start;

TInt r=start.GetCommand(); if (KErrNone==r) r=RunTheServer(start); return (r);

}

#endif

SERVER CLASSES

205

Again, as you can see, the startup code differs for hardware process and emulator DLL builds. On the emulator, the server DLL has an entry point called WinsMain(),which takes no parameters and returns a TInt. However, the Symbian OS thread function entry point function must take a TAny* parameter, so WinsMain() is used to return the actual thread function (ThreadFunction()) by casting the function pointer to a TInt. The TAny* parameter passed to the thread function refers to the client’s TServerStart object. This is passed directly to the main server startup function, called RunTheServer(). It sounds quite complex, but as you can see, it only takes a few lines of code.

On hardware, the server is a process with E32Main() as its entry point. It simply instantiates a TServerStart object and uses GetCommand() to retrieve the client’s TThreadId and TRequestStatus. Having done so, it calls RunTheServer(). At this point, the divergence between the emulator and hardware code is at an end.

RunTheServer() first creates a cleanup stack for the server. Having done so, it calls RunTheServerL() within a TRAP harness (recall from Chapter 2 that there must be at least one top-level TRAP in the system before the cleanup stack can be used). RunTheServerL() creates and installs an active scheduler to allow it to service incoming client requests. It then creates the CServer-derived object (and any objects that uses) and calls CServer::Start() or CServer::StartL(). Once the server object is created and ready to receive connection requests, the server thread is renamed with the name of the server. This is an optional step, but it is useful when debugging panics to be able to see the name of the server thread. At this point, server startup is complete and TServerStart::SignalL() is called to notify the client that the server is fully initialized.

Having signaled the client, the Start() method is called on the active scheduler to make it ready to receive and process client requests. Within this function, the server thread enters the active scheduler wait loop, receiving and servicing client requests until CActiveScheduler::Stop() is called.

12.5 Server Classes

The fundamental server-side classes, deriving from CServer and CSharableSession, are defined as follows:

class CHerculeanServer : public CServer // Receives client requests

{

public:

static CServer* NewLC();

void AddSession();

void RemoveSession();

206

THE CLIENT–SERVER FRAMEWORK IN PRACTICE

protected:

virtual TInt RunError(TInt aError); // Overrides CActive::RunError() private:

CHerculeanServer(); void ConstructL();

virtual CSharableSession* NewSessionL(const TVersion& aVersion) const; // From CServer

private:

TInt iSessionCount; CShutdown iShutdown; };

inline CHerculeanServer::CHerculeanServer() :CServer(0, ESharableSessions){}

class CAsyncHandler; // Active object class for asynchronous requests

// Services client requests

class CHerculeanSession : public CSharableSession

{

public:

CHerculeanSession(){};

virtual void CreateL(const CServer& aServer); private:

void SlayNemeanLionL(const RMessage& aMessage); void SlayHydraL(const RMessage& aMessage);

void CaptureCeryneianHindL(const RMessage& aMessage); void SlayErymanthianBoarL(const RMessage& aMessage); void CleanAugeanStablesL(const RMessage& aMessage); void SlayStymphalianBirdsL(const RMessage& aMessage);

private:

CHerculeanSession();

inline CHerculeanServer& Server();

void ServiceL(const RMessage& aMessage); // From CSharableSession private:

CAsyncHandler* iAsyncRequestHandler;

HBufC8* iClientBuf;

};

inline CHerculeanServer& CHerculeanSession::Server()

{

return *static_cast<CHerculeanServer*>(const_cast<CServer*> (CSharableSession::Server()));

}

The CServer-derived class CHerculeanServer is the main server class; it coordinates server startup and shutdown and, as I described in the previous chapter, receives incoming requests. The system creates a single instance of CHerculeanServer on server startup. The CServer base class manages a doubly-linked list of connected client sessions. When the server class receives a client request, it passes it to the associated CHerculeanSession object for handling.

Let’s examine the code for the CHerculeanServer class in more detail (I’ve omitted some of the straightforward construction code to keep the code sample as short as possible):

SERVER CLASSES

207

//Starts the server and constructs the shutdown object, starting the

//timer to ensure that the server will exit even if the starting client

//connection fails

void CHerculeanServer::ConstructL()

{

StartL(KServerName);

iShutdown.ConstructL();

iShutdown.Start(); // In case the client session fails to connect

}

void CHerculeanServer::AddSession()

{

++iSessionCount;

iShutdown.Cancel();// Cancel the shutdown timer now

}

//Decrement the session counter. Start the shutdown timer when the last

//client disconnects

void CHerculeanServer::RemoveSession()

{

if (--iSessionCount==0) iShutdown.Start();

}

TInt CHerculeanServer::RunError(TInt aError)

{

if (KErrBadDescriptor==aError) PanicClient(Message(),EPanicBadDescriptor);

else Message().Complete(aError);

ReStart(); // Continue reading client requests

return (KErrNone); // handled the error

}

The construction of the CHerculeanServer class is straightforward. The shutdown timer is started when the server is first constructed, in case construction of the initial client session fails. When the first session is added successfully, the session count is incremented and the shutdown timer is canceled. The server object increments and decrements the iSessionCount reference count accordingly when sessions are added and removed.

CHerculeanServer::RunError() is called if a leave occurs in CHerculeanSession::ServiceL(), that is, if one of the methods which services client requests leaves. The leave code is passed to RunError(), which should attempt to handle the leave, returning

KErrNone if it does so. CServer::RunError() was added to Symbian OS v6.0 to allow the server to manage leaves resulting from client request processing. Previously, the leaves were propagated to the active scheduler which did not have sufficient context in which to handle them.

RunError() panics the client if the leave code is KErrBadDescriptor, because this indicates that client data has been passed to the server without having been properly ”descriptorized”. This is

208

THE CLIENT–SERVER FRAMEWORK IN PRACTICE

indicative of a programming error, so the server is justified in panicking the client. Under all other circumstances, the server reports the error to the client by completing it using RMessage::Complete(). It is rarely correct to panic another thread except to indicate a programming error.

The code used by CHerculeanServer to panic the client is shown below. RMessage::Panic() uses the RThread handle it holds for the client thread to panic it and also completes the outstanding client request to allow the kernel to perform the necessary cleanup.

enum TServerPanic

{

EPanicBadDescriptor, EPanicNotSupported };

void PanicClient(const RMessage& aMessage,TServerPanic aPanic)

{

_LIT(KPanic,"HerculesServer"); aMessage.Panic(KPanic,aPanic);

}

A leave from CHerculeanSession::ServiceL() results in an early return from CServer::RunL(), which skips the call to continue requesting client messages. From Chapter 11, you’ll recall that, on receipt of a client request, CServer::RunL() calls the ServiceL() method of the associated CSharableSession-derived object. It is for this reason that RunError() must call CServer::Restart().

Moving on, let’s consider the implementation of CHerculeanSession. This consists of an implementation of the ServiceL() method, which was declared pure virtual in the CSharableSession base class, and a set of private methods to handle client requests:

void CHerculeanSession::CreateL(const CServer& aServer) {// Called by the CServer framework CSharableSession::CreateL(aServer); // Cannot leave Server().AddSession();

// Create the CAsyncHandler object (iAsyncRequestHandler)

...

}

CHerculeanSession:: CHerculeanSession()

{

Server().RemoveSession(); delete iAsyncRequestHandler; delete iClientBuf;

}

//Handle a client request

//Leaves are handled by CHerculeanServer::RunError() which is called

//by CServer::RunL()

void CHerculeanSession::ServiceL(const RMessage& aMessage)

SERVER CLASSES

209

{

 

switch (aMessage.Function())

 

{

 

case ESlayNemeanLion:

 

SlayNemeanLionL(aMessage);

break;

case ESlayHydra:

 

SlayHydraL(aMessage);

break;

case ECaptureCeryneianHind:

 

CaptureCeryneianHindL(aMessage);

break;

case ESlayErymanthianBoar:

 

SlayErymanthianBoarL(aMessage);

break;

case ECleanAugeanStables:

 

CleanAugeanStablesL(aMessage);

break;

case ECancelCleanAugeanStables:

 

CancelCleanAugeanStables();

break;

case ESlayStymphalianBirds:

 

SlayStymphalianBirdsL(aMessage);

break;

case ECancelSlayStymphalianBirds:

 

CancelSlayStymphalianBirds();

break;

case ECaptureCretanBull: // Omitted for clarity case ECaptureMaresOfDiomedes:

case EObtainGirdleOfHippolyta: case ECaptureOxenOfGeryon:

case ETakeGoldenApplesOfHesperides: case ECaptureCerberus:

default:

PanicClient(aMessage, EPanicNotSupported); break;

}

}

//p[0] contains const TDesC8&

//p[1] contains TInt

void CHerculeanSession::SlayNemeanLionL(const RMessage& aMessage)

{

const TInt KMaxLionDes = 100; TBuf8<KMaxLionDes> lionDes; aMessage.ReadL(aMessage.Ptr0(), lionDes); TInt val = aMessage.Int1();

// ... Process as necessary aMessage.Complete(KErrNone);

}

// p[0] contains TPckg<THydraData>

void CHerculeanSession::SlayHydraL(const RMessage& aMessage)

{

THydraData hydraData; TPckg<THydraData> des(hydraData); aMessage.ReadL(aMessage.Ptr0(), des);

//... Process as necessary, updates hydraData.iHeadCount

//Write hydraData update back to client aMessage.WriteL(aMessage.Ptr0(), des); aMessage.Complete(KErrNone);

}

//p[0] contains TInt&

void CHerculeanSession::CaptureCeryneianHindL(const RMessage& aMessage)

{

210 THE CLIENT–SERVER FRAMEWORK IN PRACTICE

TInt count;

// ... Process as necessary (updates count) TPckgC<TInt> countDes(count); aMessage.WriteL(aMessage.Ptr0(), countDes); aMessage.Complete(KErrNone);

}

// p[0] contains streamed CHerculesData

void CHerculeanSession::SlayErymanthianBoarL(const RMessage& aMessage)

{

HBufC8* desData = HBufC8::NewLC(KMaxCHerculesDataLength); TPtr8 readPtr(desData->Des()); aMessage.ReadL(aMessage.Ptr0(), readPtr);

CHerculesData* data = CHerculesData::NewLC(*desData); // ... Process as appropriate, passing in data aMessage.Complete(KErrNone);

}

// Asynchronous method - no client parameters

void CHerculeanSession::CleanAugeanStablesL(const RMessage& aMessage) {// Makes an asynchronous request via the CAsyncHandler active object // (initialized with aMessage to allow it to complete the client)

...

}

void CHerculeanSession::CancelCleanAugeanStablesL()

{

... // Calls Cancel() on the CAsyncHandler active object which // checks if a request is outstanding and cancels it

}

//Asynchronous method

//p[0] contains TInt

//p[1] contains TDes8&

void CHerculeanSession::SlayStymphalianBirdsL(const RMessage& aMessage)

{

TInt val0 = aMessage.Int0();

//Determine the length of the client descriptor passed to the server TInt clientDesMaxLen =

aMessage.Client().GetDesMaxLength(aMessage.Ptr1()); if (iClientBuf)

{

delete iClientBuf; iClientBuf = NULL;

}

//iClientBuf owned/destroyed by session

iClientBuf = HBufC8::NewL(clientDesMaxLen);

TPtr8 ptr(iClientBuf->Des());

aMessage.ReadL(aMessage.Ptr1(), ptr);

//Makes an asynchronous request via the CAsyncHandler active object

//which is initialized with aMessage to allow it to complete the

//client. Modifies the contents of iClientBuf and writes it back to

//the client

}

void CHerculeanSession::CancelSlayStymphalianBirdsL()

{

SERVER CLASSES

211

... // Calls Cancel() on the CAsyncHandler active object which

// checks if a request is outstanding and cancels it

}

ServiceL() consists of a switch statement that examines the client request opcode, using RMessage::Function(), and calls the associated handler method for that request. In the example code above, I’ve only shown some of the request handling methods that CHerculesSession implements.

You’ll recall that the client-side request code consisted of boilerplate ”packaging” of parameter data to pass to the server. By extension, the server-side request code unpacks the parameter data, performs the necessary processing upon it, repackages return data if necessary and notifies the client that the request has been completed. Let’s now examine each of those stages in turn.

The parameter unpacking code is fairly routine, as you can see from the implementation of CHerculeanSession::SlayNemeanLionL(). The client writes a pointer to a constant TDesC8 into the first element of the request data array. The server retrieves this data by instantiating a modifiable descriptor and using RMessage::ReadL()6 to read data from the client thread into it. The TAny pointer to the location of the client descriptor is identified in this case by use of RMessage::Ptr0() – if the descriptor had been in the second slot in the request array, RMessage::Ptr1() would have been used, and so on.

In this example, the predetermined protocol between client and server has fixed the maximum size of the client-side descriptor as KMaxLionDes bytes, so the server allocates a stack-based TBuf8 with that maximum size to receive the incoming data. However, if the size of the data is unknown at compile time, as in SlayStymphalianBirdsL(), the server must determine the size of the incoming descriptor to ensure that a sufficiently large descriptor is allocated on the server side to receive the client data. It can do this by calling RThread::GetDesMaxLength() on the client thread, passing in the pointer to the descriptor. It also needs to perform this check before writing descriptor data back to the client, to determine whether the client has allocated a large enough descriptor.

The use of a heap-based descriptor to read data from the client is more appropriate if a large or unpredictable amount of data is transferred between the client and server, because the amount of stack space available is restricted on Symbian OS.

SlayNemeanLionL() retrieves a TInt from the second element of the request data array, using RMessage::Int1(), which returns the client parameter in the second ”slot” as an integer value. Don’t let the zero-based numbering scheme confuse matters here!

6 RMessage::ReadL() performs inter-thread data transfer by calling RThread::ReadL(), as discussed in Chapter 10.

212

THE CLIENT–SERVER FRAMEWORK IN PRACTICE

Having retrieved the client parameters, the request-handling function then performs any necessary processing upon it – I’ve omitted this from the example code to keep it straightforward. SlayNemeanLionL() is a simple example because it is synchronous and doesn’t package any return data to send to the client. Thus, when the request processing is finished, the server simply notifies the client by calling RMessage::Complete(), which signals the client thread’s request semaphore to indicate request completion.

CaptureCeryneianHindL() shows the server writing data back to the client thread – in this case, it updates the integer value passed into the first element of the request data array. The server has an integer value, count, which represents the number of hinds captured. It ”descriptorizes” this value using a TPckgC and calls RMessage::WriteL() to make an inter-thread data transfer into the client thread.

Earlier, I discussed in detail how the client submitted custom objects to the server, such as those of T or C classes. I described how an object of class THydraData was marshaled into a descriptor using the

TPckg class, and in CHerculesSession::SlayHydraL() you see what happens on the other side of the client–server boundary. The server instantiates its own THydraData object, wraps it in a TPckg descriptor and then ”reconstitutes” it by reading into it the descriptor passed by the client. Having done so, the server performs the necessary processing which modifies the object. It writes the changes back to the client using RMessage::WriteL(). In a similar manner, CHerculesSession::SlayErymanthianBoarL() shows how a server receives a ”streamed” CBase-derived object in a descriptor and instantiates its own copy using the appropriate NewLC() method. This object can then be passed as a parameter to the appropriate internal handling function.

While most of the request handler methods shown are synchronous,

CleanAugeanStables() and SlayStymphalianBirdsL()are asynchronous. The server retrieves any parameters passed from the client and passes them to an active object which is responsible for submitting requests to an asynchronous service provider and handling their completion events. To avoid complicating the code example I haven’t shown the active object class here, but I discuss active objects fully in Chapters 8 and 9. The active object class must be passed a means to access the RMessage associated with the client request, which it will use to call Complete() on the client when the request has been fulfilled by the asynchronous service provider. Since it only uses the RMessage to complete the client, it is unnecessary for this class to hold a copy of the entire object. Commonly, the RMessagePtr class is used to make a copy of the client’s thread handle from the RMessage, and the RMessagePtr object is then used to notify the client of the request’s completion. Class

RMessagePtr is defined in e32std.h.