Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 43
Текст из файла (страница 43)
This API is exported byRHerculesSession, which derives from RSessionBase. Each ofthe request methods passes the associated opcode, and any parameter data, to the server via a call to the base class methodRSessionBase::SendReceive(), using the synchronous or asynchronous overload as appropriate.Here is the definition of the main client-side class (I’ve shown only sixof the Herculean labor request methods):// Forward declarations – the actual class declarations must be// accessible to both client and server codeclass CHerculesData;struct THydraData;class RHerculesSession : public RSessionBase{public:IMPORT_C TInt Connect();public:IMPORT_C TInt SlayNemeanLion(const TDesC8& aDes, TInt aVal);IMPORT_C TInt SlayHydra(THydraData& aData);IMPORT_C TInt CaptureCeryneianHind(TInt& aCaptureCount);IMPORT_C TInt SlayErymanthianBoar(const CHerculesData& aData);IMPORT_C void CleanAugeanStables(TRequestStatus& aStatus);IMPORT_C void CancelCleanAugeanStables();IMPORT_C void SlayStymphalianBirds(TInt aCount, TDes8& aData,TRequestStatus& aStatus);IMPORT_C void CancelSlayStymphalianBirds();...};I’ve included a range of parameter input, and implemented synchronous and asynchronous functions for illustration purposes.
Here arethe implementations of the request submission methods:EXPORT_C TInt RHerculesSession::SlayNemeanLion(const TDesC8& aDes,TInt aVal){const TAny* p[KMaxMessageArguments];p[0]=&aDes;p[1]=(TAny*)aVal;return (SendReceive(ESlayNemeanLion,p));}EXPORT_C TInt RHerculesSession::SlayHydra(THydraData& aData){const TAny* p[KMaxMessageArguments];TPckg<THydraData> data(aData);p[0]=&data;return (SendReceive(ESlayHydra,p));192THE CLIENT–SERVER FRAMEWORK IN PRACTICE}EXPORT_C TInt RHerculesSession::CaptureCeryneianHind(TInt& aCaptureCount){const TAny* p[KMaxMessageArguments];TPckg<TInt> countBuf(aCaptureCount);p[0]=(TAny*)&countBuf;return (SendReceive(ECaptureCeryneianHind,p));}// The implementation of RHerculesSession::SlayErymanthianBoar()// is omitted here because it is discussed later in the section// Asynchronous requestEXPORT_C void RHerculesSession::CleanAugeanStables(TRequestStatus& aStat){SendReceive(ECleanAugeanStables, 0, aStat);}// Cancels the CleanAugeanStables() asynchronous requestEXPORT_C void RHerculesSession::CancelCleanAugeanStables(){SendReceive(ECancelCleanAugeanStables, 0);}// Asynchronous requestEXPORT_C void RHerculesSession::SlayStymphalianBirds(TInt aCount,TDes8& aData, TRequestStatus& aStatus){const TAny* p[KMaxMessageArguments];p[0] = (TAny*)aCount;p[1] = &aData;SendReceive(ESlayStymphalianBirds, p, aStatus);}// Cancels the SlayStymphalianBirds() asynchronous requestEXPORT_C void RHerculesSession::CancelSlayStymphalianBirds (){// Every asynchronous request should have a cancellation methodSendReceive(ECancelSlayStymphalianBirds, 0);}You’ll understand now why I’ve called it ”boilerplate” code – there’sa lot of repetition in the methods.In each case, you’ll notice that, if any parameter data is passed to theserver with the request, the method instantiates an array of 32-bit values ofsize KMaxMessageArguments (= 4, as defined in e32std.h).
If thereare no accompanying request parameters (as in the case of the requestwith opcode ECleanAugeanStables or the cancellation methods),the array is not needed. The array is used to hold either the requestdata itself (if it can be stored in the 32-bit elements) or a pointer toa descriptor that stores the client-side data. It is the contents of thisarray that are stored in an RMessage on the server side, but there isno direct equivalent of RMessage for client-side code.
The array ispassed to RSessionBase::SendReceive() and must always be ofsize KMaxMessageArguments even when fewer parameters are passedto the server.CLIENT BOILERPLATE CODE193It is important that the client-side data passed to an asynchronousrequest must not be stack-based. This is because the server may notprocess the incoming request data until some arbitrary time after theclient issued the request. The parameters must remain in existence untilthat time – so they cannot exist on the stack in case the client-sidefunction which submitted the request returns, destroying the stack frame.The client API differentiates between non-modifiable descriptors passedto functions which pass constant data to the server and modifiable descriptors used to retrieve data from it.
However, the client-side interface codesimply passes a pointer to the descriptor as a message parameter. Serverside, this will be used in a call to RThread::ReadL() to retrieve datafrom the client or in a call to RThread::WriteL() to write data tothe client thread. The RThread methods inspect the descriptor to whichit points, to check that it appears to be a descriptor, and leave withKErrBadDescriptor if it does not.SlayNemeanLion() and CaptureCeryneianHind() show howinteger and descriptor data are passed to a server, but what about customdata? What if it has variable length or does not just contain ”flat” data,but owns pointers to other objects, as is common for a C class object?I’ll show how to pass a CBase-derived object across the client–serverboundary in SlayErymanthianBoar() shortly, but first, let’s considerhow to pass an object of a T class or a struct.RHerculesSession::SlayHydra() passes an object of typeTHydraData which is a simple struct that contains only built-intypes, defined as follows:struct THydraData{TVersion iHydraVersion;TInt iHeadCount;};TVersion is a Symbian OS class defined as follows in e32std.h:class TVersion{public:...
// Constructors omitted for claritypublic:TInt8 iMajor;TInt8 iMinor;TInt16 iBuild;};A THydraData object is thus 64 bits in size, which is too large tobe passed to the server as one of the 32-bit elements of the requestdata array. It isn’t enough to pass a pointer to the existing THydraData194THE CLIENT–SERVER FRAMEWORK IN PRACTICEobject either because, when the client and server are running in differentprocesses, the server code runs in a different virtual address space. Underthese circumstances, a C++ pointer which is valid in the client process isnot valid in the server process; any attempt to use it server-side will resultin an access violation.2Server-side code should not attempt to access a client-side objectdirectly through a C++ pointer passed from the client to server. Datatransfer between client and server must be performed using the interthread data transfer3 methods of class RThread – except when integeror boolean values are passed from the client.
The request data arraycan be used to pass up to four 32-bit values to the server, so these canbe read directly from the request message server-side. However, if theparameter is a reference which the server updates for the client (such asin the CaptureCeryneianHind() method above), the server must usea kernel-mediated transfer to write the 32-bit data back to the client.
I’lluse example code to illustrate this later in the chapter.RHerculesSession::SlayNemeanLion() transfers a descriptorparameter (const TDesC8&aDes) from the client to the server by passinga pointer to that descriptor as one of the elements of the request dataarray. However, THydraData is not a descriptor and before it is passedto the server it must be ”descriptorized”. Chapter 6 discusses the use ofthe package pointer template classes TPckg and TPckgC, which canbe used to wrap a flat data object such as THydraData with a pointerdescriptor, TPtr8. The SlayHydra() method of RHerculesSessioncreates a TPckg<THydraData> around its THydraData parameter topass to the server in the request data array.
The resulting descriptorhas a length equivalent to the size in bytes of the templated object itwraps, and the iPtr data pointer of the TPtr8 addresses the start ofthe THydraData object. Later in the chapter I’ll show how the serveraccesses the data and retrieves the THydraData object.Moving on, let’s consider how an object of a C class, containinga pointer to another object or variable-length data, is marshaled fromclient to server. Consider the CHerculesData class, which owns twoheap descriptor pointers and an integer value.
We’ve already seen thatpassing a client-side object to the server requires the entire object to be”descriptorized” for passing across the process boundary. However, asI’ve already described, pointers to data in the client address space cannot2It should be noted that standard C++ pointer access directly between the client andserver may succeed on the Windows emulator. The emulator runs as a single process witheach Symbian OS process running as a separate thread. Threads share writable memoryand therefore the address spaces of the client and server are not separated by a processboundary, but are mutually accessible.
However, this will most definitely not be the casewhen Symbian OS runs on real phone hardware, so you must not use direct pointer accessto transfer data between the client and server processes.3Where the client and server are running in different processes this transfer is, in effect,inter-process communication (IPC).CLIENT BOILERPLATE CODE195be used server-side.
Thus, for the server to use a CHerculesData object,it must receive a copy of the data each heap descriptor pointer addresses.The CHerculesData class must have utility code which puts allits member data into a descriptor client-side (”externalization”) andcorresponding code to recreate it from the descriptor server-side (”internalization”). There is a standard technique for this, as shown below:class CHerculesData : public CBase{public:IMPORT_C static CHerculesData* NewLC(const TDesC8& aDes1,const TDesC8& aDes2, TInt aVal);static CHerculesData* NewLC(const TDesC8& aStreamData);IMPORT_C ∼CHerculesData();...
// Other methods omittedpublic:// Creates an HBufC8 representation of ’this’IMPORT_C HBufC8* MarshalDataL() const;protected:// Writes ’this’ to the streamvoid ExternalizeL(RWriteStream& aStream) const;// Initializes ’this’ from streamvoid InternalizeL(RReadStream& aStream);protected:CHerculesData(TInt aVal);CHerculesData(){};void ConstructL(const TDesC8& aDes1, const TDesC8& aDes2);protected:HBufC8* iDes1;HBufC8* iDes2;TInt iVal;};// Maximum size expected for iDes1 and iDes2 in CHerculesDataconst TInt KMaxHerculesDesLen = 255;// Maximum total size expected for a CHerculesData objectconst TInt KMaxCHerculesDataLength = 520;EXPORT_C CHerculesData* CHerculesData::NewLC(const TDesC8& aDes1,const TDesC8& aDes2, TInt aVal){CHerculesData* data = new (ELeave) CHerculesData(aVal);CleanupStack::PushL(data);data->ConstructL(aDes1, aDes2);return (data);}// Creates a CHerculesData initialized with the contents of the// descriptor parameterCHerculesData* CHerculesData::NewLC(const TDesC8& aStreamData){// Reads descriptor data from a stream// and creates a new CHerculesData objectCHerculesData* data = new (ELeave) CHerculesData();CleanupStack::PushL(data);// Open a read stream for the descriptor196THE CLIENT–SERVER FRAMEWORK IN PRACTICERDesReadStream stream(aStreamData);CleanupClosePushL(stream);data->InternalizeL(stream);CleanupStack::PopAndDestroy(&stream); // finished with the streamreturn (data);}EXPORT_C CHerculesData::∼CHerculesData(){delete iDes1;delete iDes2;}CHerculesData::CHerculesData(TInt aVal):iVal(aVal){}void CHerculesData::ConstructL(const TDesC8& aDes1, const TDesC8& aDes2){iDes1 = aDes1.AllocL();iDes2 = aDes2.AllocL();}// Creates and returns a heap descriptor which holds contents of ’this’EXPORT_C HBufC8* CHerculesData::MarshalDataL() const{// Dynamic data bufferCBufFlat* buf = CBufFlat::NewL(KMaxCHerculesDataLength);CleanupStack::PushL(buf);RBufWriteStream stream(*buf); // Stream over the bufferCleanupClosePushL(stream);ExternalizeL(stream);CleanupStack::PopAndDestroy(&stream);// Create a heap descriptor from the bufferHBufC8* des = HBufC8::NewL(buf->Size());TPtr8 ptr(des->Des());buf->Read(0, ptr, buf->Size());CleanupStack::PopAndDestroy(buf); // Finished with the bufferreturn (des);}// Writes ’this’ to aStreamvoid CHerculesData::ExternalizeL(RWriteStream& aStream) const{if (iDes1) // Write iDes1 to the stream (or a NULL descriptor)aStream << *iDes1;elseaStream << KNullDesC8;if (iDes2) // Write iDes2 to the stream (or a NULL descriptor)aStream << *iDes2;elseaStream << KNullDesC8;aStream.WriteInt32L(iVal); // Write iVal to the stream}// Initializes ’this’ with the contents of aStreamCLIENT BOILERPLATE CODE197void CHerculesData::InternalizeL(RReadStream& aStream){iDes1 = HBufC8::NewL(aStream, KMaxHerculesDesLength); // Read iDes1iDes2 = HBufC8::NewL(aStream, KMaxHerculesDesLength); // Read iDes2iVal = aStream.ReadInt32L();// Read iVal}CHerculesData has a public method MarshalDataL(), whichcreates and returns a heap descriptor containing the current contents ofthe object.