Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 44
Текст из файла (страница 44)
The method creates a dynamic buffer and passes a writablestream over this buffer to its ExternalizeL() method. The protectedexternalization method then writes the data of each individual member ofCHerculesData to the stream, using the built-in stream externalizationsupport provided by Symbian OS. ExternalizeL() uses the streamoperator<< to write the heap descriptors to the stream (as I discussedin Chapter 6).Having created an in-memory stream of the contents of a CHerculesData object in the dynamic buffer, the MarshalDataL() method thenconverts it to a heap descriptor by allocating a descriptor of the appropriate size and copying the data from the dynamic buffer.Server-side, the descriptor data is read from the client thread by makingan inter-thread data transfer.
To convert the descriptorized CHerculesData back to an object of that class, the server calls the overload ofCHerculesData::NewLC() passing the descriptor which contains thedescriptorized object. This method creates a descriptor read stream andcalls InternalizeL(), which is the opposite of ExternalizeL(),and instantiates the heap descriptor member variables from the stream.Any class that holds variable-length data, such as an RArray-derivedobject or pointers to heap-based objects, must provide similar externalizeand internalize functions if objects of that type need to be marshaledbetween a client and server.Here’s the code for the RHerculesSession method which marshalsa CHerculesData object into a descriptor and passes it from client toserver using RSessionBase::SendReceive():EXPORT_C TInt RHerculesSession::SlayErymanthianBoar(const CHerculesData&aData){const TAny* p[KMaxMessageArguments];HBufC8* dataDes;TRAPD(r, dataDes = aData.MarshalDataL());if (dataDes){p[0] = dataDes;r = SendReceive(ESlayErymanthianBoar, p);delete dataDes;}return (r);}198THE CLIENT–SERVER FRAMEWORK IN PRACTICEFigure 12.1 illustrates the previous discussion and code sample.iDes1HelloCHerculesDataiDes1iDes2iVal = 5iDes1HelloCHerculesDataiDes1iDes2iVal=5iDes2World!CHerculesData::MarshalDataL()(calls CHerculesData::ExternalizeL())iDes2World!CHerculesData::NewLC()callsCHerculesData::InternalizeL()HBufC8* dataDesLENGTHHelloWorld!5iDes1iDes2iValLRSessionBase::SendReceive() ENGTHHelloWorld!5'Descriptorized' CHerculesDataCLIENTSERVERFigure 12.1 Marshaling CHerculesData from client to server within a descriptorA client and server typically run in different processes.
For thisreason, server-side code should not attempt to access a clientside object directly through a C++ pointer passed from the clientto server. Where necessary, objects must be ”descriptorized” forinter-process communication.12.3Starting the Server and Connectingto It from the ClientThe RHerculesSession constructor zeroes its iHandle value so itis clear that the RHerculesSession object is invalid and must firstconnect to the server by calling RSessionBase::CreateSession().The client-side implementation of server access code typically wrapsthis call in a method called Connect(), e.g.
RFs::Connect(), oroccasionally an Open() method. If an attempt is made to submit arequest to a server using a client session that has not yet connected to theserver, a panic occurs (KERN-EXEC 0).If the server is an essential system server, it is guaranteed to have beenstarted by the system as Symbian OS starts and will always be running.The definition of a system server, for example, the file server, is thatSTARTING THE SERVER AND CONNECTING TO IT FROM THE CLIENT199it 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 cannotfunction without it.Client connection to a system server is straightforward because theserver is already running. In effect, a Connect() method simplyneeds to call RSessionBase::CreateSession().
However, as I’vedescribed, the Hercules example server discussed in this chapter is notan essential system server; instead it is implemented as a transient server.If it is not already running when a client calls CreateSession(), thatis if no other client is connected to it, the Connect() method of theclient-side implementation must start the server process.
The server runsin a separate process on phone hardware and Symbian OS processes runin 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 requiredfor (;;){// Uses system-pool message slotsTInt 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 inthe name of the server required.
If the server is already running andcan create a new client session, this call returns KErrNone and thefunction 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, underthese circumstances, attempt to start the server. You’ll notice that themethod also checks to see if KErrServerTerminated was returnedinstead of KErrNotFound, which indicates that the client connection request was submitted just as the server was shutting down. Foreither value,4 the server should be started (or restarted) by calling4If any other error value besides KErrNotFound or KErrTerminated is returned,Connect() returns it to the caller.200THE CLIENT–SERVER FRAMEWORK IN PRACTICEStartTheServer().
I’ll discuss this function shortly – if it returnsKErrNone, the server has started successfully. The next iteration ofthe for loop submits another request to create a client session by callingRSessionBase::CreateSession().If some other client managed to start the server between this client’sfailed 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 clientdidn’t start it. So, again, the next iteration of the loop can submit a requestto create a new client session.If the server fails to start or the client connection fails, an error isreturned and the code breaks out of the loop, returning the error value toindicate that session creation failed.StartTheServer() launches the server in a new thread on theWindows emulator, or in a new, separate process on hardware runningSymbian OS.
Because the server runs differently depending on whether itis running on the emulator or a phone, the client-side server startup codeis quite complex. The code is implemented as follows (I’ve included anumber of comments to make it clear what is going on and I’ll discussthe most important features below):// Runs client-side and starts the separate server process/threadstatic TInt StartTheServer(){TRequestStatus start;TServerStart starter(start);// HerculesServer.exe or HerculesServer.dll_LIT(KServerBinaryName,"HerculesServer");const TUid KServerUid3={0x01010101}; // Temporary UIDconst TUidType serverUid(KNullUid, KNullUid, KServerUid3);#ifdef __WINS__ // On the Windows emulator the server is a DLLRLibrary lib;TInt r=lib.Load(KServerBinaryName, serverUid);if (r!=KErrNone)return (r);// The entry point returns a TInt representing the thread// function for the serverTLibraryFunction export1 = lib.Lookup(1);TThreadFunction threadFunction =reinterpret_cast<TThreadFunction>(export1());TName name(KServerName); // Defined previously// Randomness ensures a unique thread namename.AppendNum(Math::Random(), EHex);5The KErrAlreadyExists error code is returned because there must only be onecopy of the server running on the system.
Simultaneous attempts to launch two copies ofthe server process will be detected by the kernel when the second attempt is made to createa CServer object of the same name. It is this which fails with KErrAlreadyExists.STARTING THE SERVER AND CONNECTING TO IT FROM THE CLIENT201// Now create the server threadconst 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 processTInt r=server.Create(KServerBinaryName,starter.AsCommand(),serverUid);#endifif (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 itUser::WaitForRequest(threadDied);server.Kill(0); // don’t try to start the serverserver.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 processserver.Resume();User::WaitForRequest(start, threadDied);if (KRequestPending==start){// server died and signaled - startup still pendingserver.Close();return threadDied.Int();}// The server started.// Cancel the logon and consume the cancellation signalserver.LogonCancel(threadDied);server.Close(); // Don’t need this handle any moreUser::WaitForRequest(threadDied);return (KErrNone);}As you can see, StartTheServer() is responsible for creating thenew thread or process in which the server runs.
Having done so, it waitsfor notification that the server thread or process has been initialized andreturns the initialization result to its caller. You’ll notice that the clientwaits 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,202THE CLIENT–SERVER FRAMEWORK IN PRACTICEperforming only the actions necessary to initialize the server framework.Other initialization should be performed asynchronously within the serverafter it has been successfully launched.On the emulator, the new thread is created with a unique name, toavoid receiving a KErrAlreadyExists error from the kernel if theserver is restarted just after it has exited.
The thread is assigned a standardstack size, minimum and maximum heap sizes and an RLibrary handleto the DLL in which the server code resides. The thread entry function isinitialized to the first export function of the DLL, which will be discussedshortly when I come to describe the server-side startup code. The serverthread is owned by the entire emulator process (as indicated by theEOwnerProcess parameter) rather than by the client thread that createsit.