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

CLIENT BOILERPLATE CODE

191

12.2 Client Boilerplate Code

Much of the client-side implementation is made up of the API used by callers to submit requests to the server. This API is exported by

RHerculesSession, which derives from RSessionBase. Each of the request methods passes the associated opcode, and any parameter data, to the server via a call to the base class method

RSessionBase::SendReceive(), using the synchronous or asynchronous overload as appropriate.

Here is the definition of the main client-side class (I’ve shown only six of the Herculean labor request methods):

//Forward declarations – the actual class declarations must be

//accessible to both client and server code

class 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 are the 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));

192

THE 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 request

EXPORT_C void RHerculesSession::CleanAugeanStables(TRequestStatus& aStat)

{

SendReceive(ECleanAugeanStables, 0, aStat);

}

//Cancels the CleanAugeanStables() asynchronous request EXPORT_C void RHerculesSession::CancelCleanAugeanStables()

{

SendReceive(ECancelCleanAugeanStables, 0);

}

//Asynchronous request

EXPORT_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 request EXPORT_C void RHerculesSession::CancelSlayStymphalianBirds ()

{// Every asynchronous request should have a cancellation method SendReceive(ECancelSlayStymphalianBirds, 0);

}

You’ll understand now why I’ve called it ”boilerplate” code – there’s a lot of repetition in the methods.

In each case, you’ll notice that, if any parameter data is passed to the server with the request, the method instantiates an array of 32-bit values of size KMaxMessageArguments (= 4, as defined in e32std.h). If there are no accompanying request parameters (as in the case of the request with opcode ECleanAugeanStables or the cancellation methods), the array is not needed. The array is used to hold either the request data itself (if it can be stored in the 32-bit elements) or a pointer to a descriptor that stores the client-side data. It is the contents of this array that are stored in an RMessage on the server side, but there is no direct equivalent of RMessage for client-side code. The array is passed to RSessionBase::SendReceive() and must always be of size KMaxMessageArguments even when fewer parameters are passed to the server.

CLIENT BOILERPLATE CODE

193

It is important that the client-side data passed to an asynchronous request must not be stack-based. This is because the server may not process the incoming request data until some arbitrary time after the client issued the request. The parameters must remain in existence until that time – so they cannot exist on the stack in case the client-side function which submitted the request returns, destroying the stack frame.

The client API differentiates between non-modifiable descriptors passed to functions which pass constant data to the server and modifiable descriptors used to retrieve data from it. However, the client-side interface code simply passes a pointer to the descriptor as a message parameter. Serverside, this will be used in a call to RThread::ReadL() to retrieve data from the client or in a call to RThread::WriteL() to write data to the client thread. The RThread methods inspect the descriptor to which it points, to check that it appears to be a descriptor, and leave with

KErrBadDescriptor if it does not.

SlayNemeanLion() and CaptureCeryneianHind() show how integer and descriptor data are passed to a server, but what about custom data? 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–server boundary in SlayErymanthianBoar() shortly, but first, let’s consider how to pass an object of a T class or a struct.

RHerculesSession::SlayHydra() passes an object of type

THydraData which is a simple struct that contains only built-in types, 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 clarity

public:

TInt8 iMajor;

TInt8 iMinor;

TInt16 iBuild;

};

A THydraData object is thus 64 bits in size, which is too large to be passed to the server as one of the 32-bit elements of the request data array. It isn’t enough to pass a pointer to the existing THydraData

194

THE CLIENT–SERVER FRAMEWORK IN PRACTICE

object either because, when the client and server are running in different processes, the server code runs in a different virtual address space. Under these circumstances, a C++ pointer which is valid in the client process is not valid in the server process; any attempt to use it server-side will result in an access violation.2

Server-side code should not attempt to access a client-side object directly through a C++ pointer passed from the client to server. Data transfer between client and server must be performed using the interthread data transfer3 methods of class RThread – except when integer or boolean values are passed from the client. The request data array can be used to pass up to four 32-bit values to the server, so these can be read directly from the request message server-side. However, if the parameter is a reference which the server updates for the client (such as in the CaptureCeryneianHind() method above), the server must use a kernel-mediated transfer to write the 32-bit data back to the client. I’ll use example code to illustrate this later in the chapter.

RHerculesSession::SlayNemeanLion() transfers a descriptor parameter (const TDesC8&aDes) from the client to the server by passing a pointer to that descriptor as one of the elements of the request data array. However, THydraData is not a descriptor and before it is passed to the server it must be ”descriptorized”. Chapter 6 discusses the use of the package pointer template classes TPckg and TPckgC, which can be used to wrap a flat data object such as THydraData with a pointer descriptor, TPtr8. The SlayHydra() method of RHerculesSession creates a TPckg<THydraData> around its THydraData parameter to pass to the server in the request data array. The resulting descriptor has a length equivalent to the size in bytes of the templated object it wraps, and the iPtr data pointer of the TPtr8 addresses the start of the THydraData object. Later in the chapter I’ll show how the server accesses the data and retrieves the THydraData object.

Moving on, let’s consider how an object of a C class, containing a pointer to another object or variable-length data, is marshaled from client to server. Consider the CHerculesData class, which owns two heap descriptor pointers and an integer value. We’ve already seen that passing a client-side object to the server requires the entire object to be ”descriptorized” for passing across the process boundary. However, as I’ve already described, pointers to data in the client address space cannot

2 It should be noted that standard C++ pointer access directly between the client and server may succeed on the Windows emulator. The emulator runs as a single process with each Symbian OS process running as a separate thread. Threads share writable memory and therefore the address spaces of the client and server are not separated by a process boundary, but are mutually accessible. However, this will most definitely not be the case when Symbian OS runs on real phone hardware, so you must not use direct pointer access to transfer data between the client and server processes.

3 Where the client and server are running in different processes this transfer is, in effect, inter-process communication (IPC).

CLIENT BOILERPLATE CODE

195

be 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 all its member data into a descriptor client-side (”externalization”) and corresponding 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 omitted public:

// Creates an HBufC8 representation of ’this’ IMPORT_C HBufC8* MarshalDataL() const;

protected:

// Writes ’this’ to the stream

void ExternalizeL(RWriteStream& aStream) const; // Initializes ’this’ from stream

void 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 CHerculesData const TInt KMaxHerculesDesLen = 255;

//Maximum total size expected for a CHerculesData object const 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 parameter

CHerculesData* CHerculesData::NewLC(const TDesC8& aStreamData) {// Reads descriptor data from a stream

// and creates a new CHerculesData object CHerculesData* data = new (ELeave) CHerculesData(); CleanupStack::PushL(data);

// Open a read stream for the descriptor

196

THE CLIENT–SERVER FRAMEWORK IN PRACTICE

RDesReadStream stream(aStreamData); CleanupClosePushL(stream); data->InternalizeL(stream);

CleanupStack::PopAndDestroy(&stream); // finished with the stream return (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 buffer

CBufFlat* buf = CBufFlat::NewL(KMaxCHerculesDataLength); CleanupStack::PushL(buf);

RBufWriteStream stream(*buf); // Stream over the buffer CleanupClosePushL(stream);

ExternalizeL(stream);

CleanupStack::PopAndDestroy(&stream);

// Create a heap descriptor from the buffer HBufC8* des = HBufC8::NewL(buf->Size()); TPtr8 ptr(des->Des());

buf->Read(0, ptr, buf->Size()); CleanupStack::PopAndDestroy(buf); // Finished with the buffer return (des);

}

// Writes ’this’ to aStream

void CHerculesData::ExternalizeL(RWriteStream& aStream) const

{

if (iDes1) // Write iDes1 to the stream (or a NULL descriptor) aStream << *iDes1;

else

aStream << KNullDesC8;

if (iDes2) // Write iDes2 to the stream (or a NULL descriptor) aStream << *iDes2;

else

aStream << KNullDesC8;

aStream.WriteInt32L(iVal); // Write iVal to the stream

}

// Initializes ’this’ with the contents of aStream

CLIENT BOILERPLATE CODE

197

void CHerculesData::InternalizeL(RReadStream& aStream)

{

iDes1 = HBufC8::NewL(aStream, KMaxHerculesDesLength); // Read iDes1 iDes2 = HBufC8::NewL(aStream, KMaxHerculesDesLength); // Read iDes2 iVal = aStream.ReadInt32L(); // Read iVal

}

CHerculesData has a public method MarshalDataL(), which creates and returns a heap descriptor containing the current contents of the object. The method creates a dynamic buffer and passes a writable stream over this buffer to its ExternalizeL() method. The protected externalization method then writes the data of each individual member of CHerculesData to the stream, using the built-in stream externalization support provided by Symbian OS. ExternalizeL() uses the stream operator<< to write the heap descriptors to the stream (as I discussed in Chapter 6).

Having created an in-memory stream of the contents of a CHerculesData object in the dynamic buffer, the MarshalDataL() method then converts 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 making an inter-thread data transfer. To convert the descriptorized CHerculesData back to an object of that class, the server calls the overload of CHerculesData::NewLC() passing the descriptor which contains the descriptorized object. This method creates a descriptor read stream and calls 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-derived object or pointers to heap-based objects, must provide similar externalize and internalize functions if objects of that type need to be marshaled between a client and server.

Here’s the code for the RHerculesSession method which marshals a CHerculesData object into a descriptor and passes it from client to server 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);

}