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

198

THE CLIENT–SERVER FRAMEWORK IN PRACTICE

Figure 12.1 illustrates the previous discussion and code sample.

 

CHerculesData

iDes1

CHerculesData

 

iDes1

 

 

 

Hello

 

 

 

Hello

 

iDes1

 

 

iDes1

 

 

 

iDes2

 

iDes2

iDes2

 

iDes2

 

iVal = 5

 

iVal = 5

 

 

 

World!

World!

 

 

 

 

 

 

CHerculesData::MarshalDataL()

 

CHerculesData::NewLC()

 

(calls CHerculesData::ExternalizeL())

 

 

 

 

calls

 

 

 

 

 

 

CHerculesData::InternalizeL()

HBufC8* dataDes

 

 

 

 

 

 

L

 

 

 

L

 

 

 

E

 

 

RSessionBase::SendReceive() E

 

 

 

N

Hello

World!

5

N

Hello

World!

5

G

G

 

 

 

 

 

 

T

 

 

 

T

 

 

 

H

 

 

 

H

 

 

 

 

iDes1

iDes2

iVal

 

 

 

 

 

'Descriptorized' CHerculesData

 

 

 

 

 

 

 

CLIENT

SERVER

 

 

 

Figure 12.1 Marshaling CHerculesData from client to server within a descriptor

A client and server typically run in different processes. For this reason, server-side code should not attempt to access a clientside object directly through a C++ pointer passed from the client to server. Where necessary, objects must be ”descriptorized” for inter-process communication.

12.3Starting the Server and Connecting to It from the Client

The RHerculesSession constructor zeroes its iHandle value so it is clear that the RHerculesSession object is invalid and must first connect to the server by calling RSessionBase::CreateSession(). The client-side implementation of server access code typically wraps this call in a method called Connect(), e.g. RFs::Connect(), or occasionally an Open() method. If an attempt is made to submit a request to a server using a client session that has not yet connected to the server, a panic occurs (KERN-EXEC 0).

If the server is an essential system server, it is guaranteed to have been started by the system as Symbian OS starts and will always be running. The definition of a system server, for example, the file server, is that

STARTING THE SERVER AND CONNECTING TO IT FROM THE CLIENT 199

it is required by the system. If a system server panics for some reason, Symbian OS is forced to reboot and restart the server because it cannot function without it.

Client connection to a system server is straightforward because the server is already running. In effect, a Connect() method simply needs to call RSessionBase::CreateSession(). However, as I’ve described, the Hercules example server discussed in this chapter is not an essential system server; instead it is implemented as a transient server. If it is not already running when a client calls CreateSession(), that is if no other client is connected to it, the Connect() method of the client-side implementation must start the server process. The server runs in a separate process on phone hardware and Symbian OS processes run in separate threads within the single process of the Windows emulator.

Here is the implementation of Connect() for the Hercules server:

// The server’s identity within the client – server framework _LIT(KServerName,"HerculesServer");

EXPORT_C TInt RHerculesSession::Connect()

{

TInt retry=2; // A maximum of two iterations of the loop are required for (;;)

{

// Uses system-pool message slots

TInt r=CreateSession(KServerName,TVersion(1,0,0)); if ( (KErrNotFound!=r) && (KErrServerTerminated!=r) )

return (r);

if (--retry==0) return (r);

r=StartTheServer();

if ( (KErrNone!=r) && (KErrAlreadyExists!=r) ) return (r);

}

}

The method calls RSessionBase::CreateSession(), passing in the name of the server required. If the server is already running and can create a new client session, this call returns KErrNone and the function returns successfully, having connected a new session. However, if the server is not running, CreateSession() returns KErrNotFound. The Connect() method must check for this result and, under these circumstances, attempt to start the server. You’ll notice that the method also checks to see if KErrServerTerminated was returned instead of KErrNotFound, which indicates that the client connection request was submitted just as the server was shutting down. For either value,4 the server should be started (or restarted) by calling

4 If any other error value besides KErrNotFound or KErrTerminated is returned, Connect() returns it to the caller.

200

THE CLIENT–SERVER FRAMEWORK IN PRACTICE

StartTheServer(). I’ll discuss this function shortly – if it returns KErrNone, the server has started successfully. The next iteration of the for loop submits another request to create a client session by calling

RSessionBase::CreateSession().

If some other client managed to start the server between this client’s failed attempt to create a session on it and the call to StartTheServer(), it returns KErrAlreadyExists. Under the circumstances, this is not an error,5 because the server is now running, even if the client didn’t start it. So, again, the next iteration of the loop can submit a request to create a new client session.

If the server fails to start or the client connection fails, an error is returned and the code breaks out of the loop, returning the error value to indicate that session creation failed.

StartTheServer() launches the server in a new thread on the Windows emulator, or in a new, separate process on hardware running Symbian OS. Because the server runs differently depending on whether it is running on the emulator or a phone, the client-side server startup code is quite complex. The code is implemented as follows (I’ve included a number of comments to make it clear what is going on and I’ll discuss the most important features below):

// Runs client-side and starts the separate server process/thread static TInt StartTheServer()

{

TRequestStatus start;

TServerStart starter(start);

// HerculesServer.exe or HerculesServer.dll _LIT(KServerBinaryName,"HerculesServer");

const TUid KServerUid3={0x01010101}; // Temporary UID const TUidType serverUid(KNullUid, KNullUid, KServerUid3);

#ifdef __WINS__ // On the Windows emulator the server is a DLL RLibrary lib;

TInt r=lib.Load(KServerBinaryName, serverUid); if (r!=KErrNone)

return (r);

//The entry point returns a TInt representing the thread

//function for the server

TLibraryFunction export1 = lib.Lookup(1);

TThreadFunction threadFunction =

reinterpret_cast<TThreadFunction>(export1()); TName name(KServerName); // Defined previously

// Randomness ensures a unique thread name name.AppendNum(Math::Random(), EHex);

5 The KErrAlreadyExists error code is returned because there must only be one copy of the server running on the system. Simultaneous attempts to launch two copies of the server process will be detected by the kernel when the second attempt is made to create a CServer object of the same name. It is this which fails with KErrAlreadyExists.

STARTING THE SERVER AND CONNECTING TO IT FROM THE CLIENT 201

// Now create the server thread

const TInt KMinServerHeapSize=0x1000; const TInt KMaxServerHeapSize=0x1000000;

RThread server;

r = server.Create(name, threadFunction, KDefaultStackSize, &starter, &lib, NULL,

KMinServerHeapSize, KMaxServerHeapSize, EOwnerProcess);

lib.Close(); // The server thread has a handle to the library now

#else

//

Phone hardware - comparatively easy –

RProcess server;

//

just create a new process

TInt r=server.Create(KServerBinaryName,starter.AsCommand(), serverUid);

#endif

if (KErrNone!=r) return r;

TRequestStatus threadDied; server.Logon(threadDied);

if (KRequestPending!=threadDied)

{// logon failed - server isn’t running yet // Manage the thread signal by consuming it User::WaitForRequest(threadDied);

server.Kill(0); // don’t try to start the server server.Close();

return threadDied.Int(); // return the error

}

//Start the server thread or process and wait for startup or thread

//death notification

//This code is identical for both the emulator thread

//and the phone hardware process

server.Resume(); User::WaitForRequest(start, threadDied); if (KRequestPending==start)

{// server died and signaled - startup still pending server.Close();

return threadDied.Int();

}

//The server started.

//Cancel the logon and consume the cancellation signal server.LogonCancel(threadDied);

server.Close(); // Don’t need this handle any more User::WaitForRequest(threadDied);

return (KErrNone);

}

As you can see, StartTheServer() is responsible for creating the new thread or process in which the server runs. Having done so, it waits for notification that the server thread or process has been initialized and returns the initialization result to its caller. You’ll notice that the client waits for server notification by calling User::WaitForRequest(), which means that the client thread is blocked until the server responds. For this reason, server initialization should be as rapid as possible,

202

THE CLIENT–SERVER FRAMEWORK IN PRACTICE

performing only the actions necessary to initialize the server framework. Other initialization should be performed asynchronously within the server after it has been successfully launched.

On the emulator, the new thread is created with a unique name, to avoid receiving a KErrAlreadyExists error from the kernel if the server is restarted just after it has exited. The thread is assigned a standard stack size, minimum and maximum heap sizes and an RLibrary handle to the DLL in which the server code resides. The thread entry function is initialized to the first export function of the DLL, which will be discussed shortly when I come to describe the server-side startup code. The server thread is owned by the entire emulator process (as indicated by the EOwnerProcess parameter) rather than by the client thread that creates it. This means that the client thread can terminate but the server thread will continue to run, allowing other client threads to access the server without it dying unexpectedly.

On a real Symbian OS phone, the server is launched as a process, the code for which is significantly more straightforward than for emulator thread startup. Once the server thread or process has started, the client thread calls Logon() in case a failure occurs before server initialization function is complete. The Logon() call ensures that the client thread is notified if the server dies before the server startup TRequestStatus is signaled. This prevents the client thread from hanging indefinitely if server initialization fails. Once the server entry point signals the client and sets the value of start to something other than KRequestPending, the Logon() notification is canceled. I’ll discuss how the server-side code manages initialization later in this chapter.

The client thread passes a TRequestStatus to the server for notification when its initialization is complete. A TServerStart object (”starter”) is used to transfer the client’s TRequestStatus object (”start”) to the server. The TServerStart class is defined and implemented as follows:

class TServerStart

{

public: TServerStart() {};

TServerStart(TRequestStatus& aStatus);

TPtrC AsCommand() const;

TInt GetCommand(); void SignalL();

private: TThreadId iId;

TRequestStatus* iStatus; };

inline TServerStart::TServerStart(TRequestStatus& aStatus) :iId(RThread().Id()),iStatus(&aStatus){aStatus=KRequestPending;}