Symbian OS C++ for Mobile Phones VOL 1 PHẦN 6 pps

73 300 0
Symbian OS C++ for Mobile Phones VOL 1 PHẦN 6 pps

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

// Update view iAppView->DrawNow(); } The normal sequence of processing here is that I invoke the dialog (using the terse syntax that was covered in Chapter 10), write the file, and redraw the app view to reflect the new iText value. If WriteFileL() leaves, I handle it by deleting iText and setting its pointer to 0. I also delete the file. Then I redraw the app view and use User::Leave() to propagate the error so that Uikon can display an error message corresponding to the error code. Test this, if you like, by inserting User::Leave(KErrDiskFull) somewhere in WriteFileL(). The trap handler has one virtue: it lets the user know what's going on. It's still not good enough for serious application use, though, because this approach loses user and file data. It's a cardinal rule that Symbian OS shouldn't do that. A better approach would be to do the following:  Write the new data to a temporary file: if this fails, keep the user data in RAM, but delete the temporary file.  Delete the existing file: this is very unlikely to fail, but if it does, keep the user data in RAM but delete the temporary file.  Rename the temporary file: this is very unlikely to fail, and if it does, we're in trouble. Keep the user data in RAM anyway. I would need to restructure my program to achieve this; the application architecture's framework for load/save and embeddable documents looks after these issues for you. For database-type documents, you face these issues with each entry in the database. They are managed by the permanent file store class, which we'll encounter below. 13.4.3 Reading it Back The code to read the data we have written is similar. Firstly, we read the filename from a dialog and then use OkToExitL() to do some checking. This time, the code is much easier and I'll present it in one segment: TBool CExampleReadFileDialog::OkToExitL(TInt /* aKeycode */) // Termination { // Get filename CEikFileNameEditor* fnamed=static_cast<CEikFileNameEditor*> (Control(EExampleControlIdFileName)); HBufC* fileName = fnamed->GetTextInHBufL(); // Check it's even been specified if(!fileName) { TryChangeFocusToL(EExampleControlIdFileName); iEikonEnv- >LeaveWithInfoMsg(R_EIK_TBUF_NO_FILENAME_SPECIFIED); } CleanupStack::PushL(fileName); // Check it's a valid filename if(!iCoeEnv->FsSession().IsValidName(*fileName)) { TryChangeFocusToL(EExampleControlIdFileName); iEikonEnv->LeaveWithInfoMsg(R_EIK_TBUF_INVALID_FILE_NAME); } // Check whether it's going to be possible to create the file for reading RFile file; User::LeaveIfError(file.Open(iCoeEnv->FsSession(), *fileName, EFileRead)); file.Close(); // Finished with user interaction: communicate parameters and return delete iAppUi->iFileName; iAppUi->iFileName = fileName; CleanupStack::Pop(); // fileName return ETrue; } As before, the job of OkToExitL() is to check that the user's input is sensible. This function checks:  that a filename has been specified,  that the filename is valid,  that it's going to be possible to read the file: I use RFile::Open()for this and leave if there was any error. Assuming all is well, control returns to my command handler that processes it using: void CExampleAppUi::CmdReadFileL() { // Create a read stream on the file RFileReadStream reader; reader.PushL(); // Reader on cleanup stack User::LeaveIfError(reader.Open(iCoeEnv->FsSession(), *iFileName, EFileRead)); // Read the text HBufC* string = HBufC::NewL(reader, 10000); delete iText; iText = string; // Finish CleanupStack::PopAndDestroy(); // Reader } The code is largely a mirror image of the code I used to write the data:  I create an RFileReadStream object.  Instead of using a >> operator to read the string that I wrote with <<, I use HBufC::NewL(RReadStream&, TInt). This function takes a peek into the descriptor I wrote, checks how long it is and allocates an HBufC that will be big enough to contain it (provided it's smaller than maximum length I also pass – in this case, 10 000 characters). It then reads the data.  I don't need to commit a read stream because I've got all the data I want and I'm not writing anything. So I simply close with Cleanup-Stack::PopAndDestroy(). RFileReadStream is a mirror to RFileWriteStream and as you might expect, it's derived from RReadStream. RReadStream contains many >> operators and you can add support for reading to a class by coding InternalizeL(). The only real issue in the code above was predicting how long the string would be. HBufC::NewL(RReadStream&, TInt) reads the descriptor that was written previously when *iText was written. Before allocating the HBufC, this function:  checks the length indicated in the stream,  returns KErrCorrupt if the length is longer than the maximum I passed or in various other circumstances where the length in the stream can't be valid. 13.4.4 Parsing Filenames The streams example shows how to use a TParsePtrC to crack a filename into its constituent parts. Here's some code to display all four parts of a filename in a dialog: void CExampleParseDialog::PreLayoutDynInitL() { TParsePtrC parser(*iAppUi->iFileName); CEikEdwin* edwin=static_cast<CEikEdwin*> (Control(EExampleControlIdDrive)); edwin->SetTextL(&parser.Drive()); edwin=static_cast<CEikEdwin*>(Control(EExampleControlIdPath)); edwin->SetTextL(&parser.Path()); edwin=static_cast<CEikEdwin*>(Control(EExampleControlIdPath)); edwin->SetTextL(&parser.Name()); edwin=static_cast<CEikEdwin*>(Control(EExampleControlIdPath)); edwin->SetTextL(&parser.Ext()); } The interesting thing here is that the TParsePtr constructor causes TParsePtr simply to store a reference to the filename string in iFile- Name. Because TParsePtr is essentially only a pointer, it uses very little space on the stack. I can then retrieve all its constituent parts using functions like Drive(). For file manipulation, I need to use more space. Here's how I could find the name of my resource file: TFileName appFileName = Application()->AppFullName(); TParse fileNameParser; fileNameParser.SetNoWild(_L(".rsc"), &appFileName, NULL); TPtrC helpFileFullName = fileNameParser.FullName(); First, I get my application's full name into a TFileName. If I installed to c:, it's c:\system\apps\streams\streams.app. Then I set up a TParse to parse the name: its first argument is .rsc, the second argument is my application name, and the third argument is null. Finally, I ask the TParse to return the full name of my file, which is calculated as above by scanning each of the three parameters, so it's c:\system\apps\streams\streams.rsc. This time, I had to change the data rather than simply pointing to it, so I couldn't use a TParsePtr. Having both a TFileName and a TParse on the stack uses a lot of room (over 1k). You need to avoid this except where it's both necessary (as here) and safe (meaning you don't then call many more functions that are likely to have significant stack requirements). 13.4.5 Summary of the File APIs Symbian OS file APIs contain all the functions you would expect of a conventional file system. We use RFs, TParse, and RFile functions to manipulate the file system, files, and directories. We also use RFs to ensure that our client program can communicate with the file server. But we rarely use RFile::Write() or RFile::Read() for accessing file-based data. Instead, we usually use streams. 13.5 Streams In the streams example, we got our first sight of the central classes for data management:  RWriteStream, which externalizes objects to a stream.  RReadStream, which internalizes objects from a stream. 13.5.1 External and Internal Formats Data stored in program RAM is said to be in internal format. Endian-ness, string representations, pointers between objects, padding between class members, and internally calculated values are all determined by the CPU type, C++ compiler and program implementation. Data stored in a file or sent via a communications link is said to be in external format. The actual sequence of bits and bytes matters, including string representation and endian-ness. You can't have pointers – instead, you have to serialize an internal object network into an external stream and de-serialize when you internalize again. Compression or encryption may also be used for the external format. Important You should distinguish carefully between internal and external formats. Never 'struct dump' (that is, never send your program's structs literally) when sending data to or over an external medium. For reference, the Symbian OS emulator and ARM platform implementations have only a couple of internal format differences, such as:  64-bit IEEE 754 double-precision, floating-point numbers are stored with different endian-ness on ARM and x86 architectures.  ARM requires that all 32-bit data be 32-bit aligned, whereas x86 does not. Therefore, ARM data structures potentially include padding that isn't present in their x86 equivalents. 13.5.2 Ways to Externalize and Internalize Data We have two ways to externalize and (implicitly) three ways to internalize:  You can use insertion and extraction operators: externalize with stream << object and internalize with stream >> object(remember these operators can leave).  You can externalize with object.ExternalizeL(stream) and internalize with object.InternalizeL(stream).  You can incorporate allocation, construction, and internalization into a single function of the form object = class::NewL(stream). There are, in fact, many write stream and read stream classes that derive from RWriteStream and RReadStream, and access streams stored in different objects. These objects include  files, as we have just seen;  memory: a fixed area of memory that's described by a descriptor or a (pointer, length) pair; or an expandable area of memory described by a CBufBase (see Chapter 8);  stream stores, which I'll describe below;  dictionary stores, which I'll also describe below. Note Some streams exist to perform preprocessing before writing to other streams. An example of this is REncryptStream, which encrypts data before writing it to a write stream, and RDecryptStream, which decrypts data just read from a read stream. To externalize, you always need an RWriteStream: in the code fragments below, writer could be an object of any class derived from RWriteStream. To internalize, you always need an RReadStream: in the code fragments below, reader could be an object of any class derived from RReadStream. << and >> operators To externalize a built-in type, you can use <<: TInt32 x; writer << x; TBufC <20> text = KText; writer << text; To internalize again, you can use >>: TInt32 x; reader >> x; TBuf <20> text; reader >> text; However, you can't always use << and >>. The semantics of TInt specify only that it must be at least 32 bits; it may be longer. Furthermore, users may employ TInts to represent quantities that are known to require only, say, 8 bits in external format. As the application programmer, you know the right number of bits and the stream doesn't try to second-guess you. If you write this TInt i; writer << i; the stream class doesn't know what to do. You will get a compiler error. If you find yourself in this situation, you can either cast your TInt to the type you want to use or use one of the specific write or read functions described below. Important You cannot externalize a TInt using << or internalize it using >>.You must choose a function that specifies an external size for your data. WriteXxxL() and ReadXxxL() functions If you want to be very specific about how your data is externalized, you can use the WriteXxxL() and ReadXxxL() member functions of RWriteStream and RReadStream. Here's some code: TInt i = 53; writer.WriteInt8L(i); TInt j = reader.ReadInt8L(); By doing this, it's clear that you mean to use an 8-bit external format. Here's the complete set of WriteXxxL() and ReadXxxL()functions: RwriteStream Functions RReadStream Functions <<Type External Format WriteL() ReadL() Data in internal format WriteL(RreadStream&) ReadL(RwriteStream&) WriteInt8L() ReadInt8L() TInt8 8-bit signed integer WriteInt16L() ReadInt16L() TInt16 16-bit signed integer, bytes stored little- endian WriteInt32L() ReadInt32L() TInt32 32-bit signed integer, bytes stored little- RwriteStream Functions RReadStream Functions <<Type External Format endian WriteUint8L() ReadUint8L() TUint8 8-bit unsigned integer WriteUint16L() ReadUint16L() TUint16 16-bit unsigned integer, bytes stored little- endian WriteUint32L() ReadUint32L() TUint32 32-bit unsigned integer, bytes stored little- endian WriteReal32L() ReadReal32L() TReal32 32-bit IEEE754 single-precision floating point WriteReal64L() ReadReal64L() TReal, TReal64 64-bit IEEE754 bouble-precision floating point If you use << and >> on built-in types, it will ultimately call these functions. The '<< type' column shows what Symbian OS data type will invoke these functions if used with the << and >> operators. Raw data The WriteL() and ReadL() functions for raw data deserve a closer look. Here are the WriteL() functions, as defined in the header file s32strm.h: class RWriteStream { public: IMPORT_C void WriteL(const TDesC8& aDes); IMPORT_C void WriteL(const TDesC8& aDes, TInt aLength); IMPORT_C void WriteL(const TUint8* aPtr, TInt aLength); // IMPORT_C void WriteL(const TDesC16& aDes); IMPORT_C void WriteL(const TDesC16& aDes, TInt aLength); IMPORT_C void WriteL(const TUint16* aPtr, TInt aLength); These functions simply write the data specified, according to the following rules:  WriteL(const TDesC8& aDes, TInt aLength) writes aLength bytes from the beginning of the specified descriptor.  Without the aLength parameter, the whole descriptor is written.  The const TUint8* variant writes aLength bytes from the pointer specified.  The const TDesC16 and const TUint16* variants write Unicode characters (with little-endian byte order) instead of bytes. RReadStream comes with similar (though not precisely symmetrical) functions: class RReadStream { public: IMPORT_C void ReadL(TDes8& aDes); IMPORT_C void ReadL(TDes8& aDes, TInt aLength); IMPORT_C void ReadL(TDes8& aDes, TChar aDelim); IMPORT_C void ReadL(TUint8* aPtr, TInt aLength); IMPORT_C void ReadL(TInt aLength); // IMPORT_C void ReadL(TDes16& aDes); IMPORT_C void ReadL(TDes16& aDes, TInt aLength); IMPORT_C void ReadL(TDes16& aDes, TChar aDelim); IMPORT_C void ReadL(TUint16* aPtr, TInt aLength); The problem when reading is to know when to stop. When you're writing, the descriptor length (or the aLength parameter) specifies the data length. When you're reading, the rules work like this:  The TDes8& aDes format passes a descriptor whose MaxLength()bytes will be read.  If you specify aLength explicitly, then that number of bytes will be read.  If you specify a delimiter character, the stream will read up to and including that character. If the MaxLength() of the target descriptor is encountered before the delimiter character, reading stops after MaxLength() characters – nothing is read and thrown away. Like all other ReadXxxL() functions, these functions will leave with KErrEof (end of file) if the end of file is encountered during the read operation. You should use these raw data functions with great care. Any data that you externalize with WriteL() is effectively struct-dumped into the stream. This is fine provided that the data is already in external format. Be sure that it is! When you internalize with ReadL(), you must always have a strategy for dealing with the anticipated maximum length of data. For example, you could decide that it would be unreasonable to have more than 10 000 bytes in a particular string and so you check the length purportedly given and if you find it's more than 10 000 you leave with KErrCorrupt. That's what HBufC::AllocL(RReadStream&,TInt) does. Strings You'll remember that I used this, writer.iObj << *iText; to externalize the content of the string in the streams program in which iText was an HBufC*. This doesn't match against any of the basic types externalized using an RWriteStream::WriteXxxL() function. Instead, it uses C++ templates to match against an externalizer for descriptors that write a header and then the descriptor data. To internalize a descriptor externalized in this way, if the descriptor is short and of bounded length, you can use >> to internalize again: TBuf<20> text; reader.iObj >> text; But if the length is variable you can internalize to a new HBufC of exactly the right length, which is the technique I used in streams: iText = HBufC::NewL(reader.iObj, 10000); In either case, the Symbian OS C++ framework uses an internalizer for descriptors to reinternalize the data. The internalizer reads the header that contains information about the descriptor's character width (8 or 16 bits) and length (in characters). You get panicked if the character width of the descriptor that was externalized doesn't match the descriptor type to which you're internalizing. The length is used to determine how much data to read. It's possible to externalize strings using two WriteL() functions (one for the length of the data and another for the data itself) and then reinternalize them by reading the length and the data. But it's better to use the << operator to externalize and either >> or HBufC::NewL(RReadStream&) to internalize, because the code is less difficult, but also, more importantly because you'll get standard Unicode compression (defined by the Unicode consortium) on data read and written this way. Note You don't get this compression when using WriteL(TDesC16&). The standard Unicode compression scheme involves state, but WriteL() is of necessity stateless. ExternalizeL() and InternalizeL() functions If you have an object of some class type, you need to write your own functions to enable that object to be externalized and internalized. These functions must have the following prototypes: class Foo { public: void ExternalizeL(RWriteStream& aStream) const; void InternalizeL(RReadStream& aStream); }; A general template for operator<<() ensures that you can externalize a Foo using either this: Foo foo; foo.ExternalizeL(writer); or this: writer << foo; A similar template exists for operator>>(). The ExternalizeL() and InternalizeL() functions are not virtual and there's no implication that Foo is derived from any particular base class or that it has to be a C, T, or R class. You then have to implement your own code to externalize and internalize the class. Here's some externalizing and internalizing code from my Battleships application: void CGameController::ExternalizeL(RWriteStream& aStream) const { aStream.WriteUint8L(iState); aStream.WriteUint8L(iHaveFirstMovePref); aStream.WriteUint8L(iFirstMovePref); } void CGameController::InternalizeL(RReadStream& aStream) { iState = (TState)aStream.ReadUint8L(); iHaveFirstMovePref = aStream.ReadUint8L(); iFirstMovePref = (TFirstMovePref)aStream.ReadUint8L(); } The patterns here are characteristic:  The two functions mirror each other closely.  I know that all my data can be externalized into 8-bit unsigned integers, so I use WriteUint8L() to write, and the corresponding ReadUint8L() to read.  I don't use << and >> because my internal format for all variables is a 32-bit integer – an enumeration for iState and iFirstMovePref, and a TBool for iHaveFirstMovePref.  I need some casting to convert integers back into enumerations when I read them in. If your object is more complicated, you can recursively externalize and internalize your member data. ExternalizeL(), <<, or WriteXxxL()? We have now seen three ways to externalize: Technique Application writer << object object may be a built-in integer (but not TInt), a real type, a descriptor, or any class with a properly specified ExternalizeL() member function. writer.WriteXxxL(object) object must be a suitable built-in type, or [...]... I closed the application after taking the screenshot above, then looked at the document file in c:\Documents\Boss In hex, it looks like this: 37 00 00 10 12 3A 00 10 53 02 00 10 EE 4A 28 77 31 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 53 02 00 10 20 42 4F 53 53 2E 61 70 BOSS.ap 70 04 53 02 00 10 14 00 00 00 34 3A 00 10 24 00 p 00 00 This file consists of the following: A 16 -byte... and a checksum The UIDs are 0x10000037 (for a direct file store), 0x10003a12 (for a Uikon document), and 0x10000253 (for a Boss document) The file server generates the header and checksum A 4-byte stream position indicating the beginning of a stream dictionary, which is 0x000000 31 The document data that comprises 16 consecutive bytes containing the tile values in the Boss Puzzle Since the puzzle has... it's important to bear in mind the fact that UIQ supports different color depths for items shown on screen Symbian OS provides support from 1- bit color up to 24-bit color, and individual phones will vary in terms of the color depth supported by their hardware Most current Symbian OS phones support 12 -bit color (2 12 = 4,0 96 colors) on screen However, there is a trade-off between battery-life and RAM... itself 13 .6 .1 Direct File Stores Let's consider the file format of the Boss Puzzle, that's delivered with the UIQ SDK The Boss Puzzle is a single-player game in which you move the tiles around: Figure 13 .7 If you want to build and launch this yourself, you'll find it in the UIQ C++ SDK in the directory tree headed by \UIQExamples\papers\boss If you build the engine\v1, view\v3, and quartz\v7 projects for. .. resource file for the application to include specifications for the button bar Change the project specification (.mmp) file to enable conversion of your Windows bitmaps to Symbian OS format files during the application build process That's it! You don't need to modify any C++ at all! The (Figure 14 .1) screenshot shows the button bar of the HelloGUI application we're working toward Figure 14 .1 Like some... constraints are not too onerous and you should be able to satisfy them without too much additional effort 13 .10 Summary In this chapter, we’ve introduced the application architecture APIs for communicating with the file server, and the Symbian OS framework that deals with streams and stores Data management, in Symbian OS, is based around a conventional file system, accessed through the file server APIs In practice,... application in the Application Launcher, you need to perform the following steps: Create the icon and corresponding mask Create an AIF resource file containing language captions and usage information Add the AIF conversion process command to the application build Rebuild the application The process is illustrated in Figure 14 .6: Figure 14 .6 14 .2 .1 Creating the Icon As when we added icons to the button... generated header, which contains symbolic IDs for the resources Under Windows, bitmaps and other resources are incorporated directly into resource files Under Symbian OS, they are treated separately because the tools used to create MBMs and resource files are different This also permits finer control over the content of these files – for example, some Symbian OS phones may compress resource file text strings... 9 210 and Psion Series5) that embeds the Boss Puzzle Here's what the store layout might look like, conceptually, with a Boss document inside the Word document: Note Actually, the word processor's store format is a bit more complicated than this, but the simplified version here is enough to explain our point Figure 13 .10 The main document is a Word document that uses a direct file store As with the Boss... are simple to work with They are well suited for load/save- type applications such as the Boss Puzzle or Word For these applications, the 'real' document is in user RAM and it's saved to file in entirety (or loaded from file) when necessary When the document is saved, the old file is deleted and a new file is written again from the beginning Figure 13 .11 For database-type applications, the 'real' document . c:DocumentsBoss. In hex, it looks like this: 37 00 00 10 12 3A 00 10 53 02 00 10 EE 4A 28 77 31 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 53 02 00 10 20 42 4F 53 53 2E 61 70 .BOSS.ap. RReadStream Functions <<Type External Format endian WriteUint8L() ReadUint8L() TUint8 8-bit unsigned integer WriteUint16L() ReadUint16L() TUint 16 16 -bit unsigned integer, bytes stored. IMPORT_C void ReadL(TDes 16& amp; aDes); IMPORT_C void ReadL(TDes 16& amp; aDes, TInt aLength); IMPORT_C void ReadL(TDes 16& amp; aDes, TChar aDelim); IMPORT_C void ReadL(TUint 16* aPtr, TInt aLength);

Ngày đăng: 13/08/2014, 08:21

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan