• Aucun résultat trouvé

Streams and Persistency

Dans le document The Structure of the Book (Page 173-178)

In Delphi, streams play a considerable role in persistency. For this reason, many methods of TStream relate to saving and loading a component and its subcomponents. For example, you can store a form in a stream by writing

stream.WriteComponent(Form1);

If you examine the structure of a Delphi DFM file, you'll discover that it's really just a resource file that contains a custom format resource. Inside this resource, you'll find the component information for the form or data module and for each of the components it contains. As you would expect, the stream classes provide two methods to read and write this custom resource data for components: WriteComponentRes to store the data, and ReadComponentRes to load it.

For your experiment in memory (not involving DFM files), though, using WriteComponent is generally better suited.

After you create a memory stream and save the current form to it, the problem is how to display it. You can do this by transforming the form's binary representation to a textual representation. Even though the Delphi IDE, since version 5, can save DFM files in text format, the representation used internally for the compiled code is invariably a binary format.

The IDE can accomplish the form conversion, generally with the View as Text command of the Form Designer, and in other ways. The Delphi Bin directory also contains a command-line utility, CONVERT.EXE. Within your own code, the standard way to obtain a conversion is to call the specific VCL methods. There are four functions for converting to and from the internal object format obtained by the WriteComponent method:

procedure ObjectBinaryToText(Input, Output: TStream); overload;

procedure ObjectBinaryToText(Input, Output: TStream;

var OriginalFormat: TStreamOriginalFormat); overload;

procedure ObjectTextToBinary(Input, Output: TStream); overload;

procedure ObjectTextToBinary(Input, Output: TStream;

var OriginalFormat: TStreamOriginalFormat); overload;

Four different functions, with the same parameters and names containing the name Resource instead of Binary (as in ObjectResourceToText), convert the resource format obtained by WriteComponentRes. A final method,

TestStreamFormat, indicates whether a DFM is storing a binary or textual representation.

In the FormToText program, I've used the ObjectBinaryToText method to copy the binary definition of a form into another stream, and then I've displayed the resulting stream in a memo, as you can see in Figure 4.5. Here is the code of the two methods involved:

Figure 4.5: The textual description of a form component, displayed inside itself by the FormToText example

procedure TformText.btnCurrentClick(Sender: TObject);

var

MemStr: TStream;

begin

MemStr := TMemoryStream.Create;

try

MemStr.WriteComponent (Self);

ConvertAndShow (MemStr);

finally

ObjectBinaryToText (aStream, ConvStream);

ConvStream.Position := 0;

MemoOut.Lines.LoadFromStream (ConvStream);

finally

ConvStream.Free end;

end;

Notice that by repeatedly clicking the Current Form Object button you'll get more and more text, and the text of the memo is included in the stream. After a few times, the entire operation will become extremely slow, until the program seems to be hung up. In this code, you see some of the flexibility of using streams you can write a generic procedure that you can use to convert any stream.

Note

It's important to stress that after you've written data to a stream, you must explicitly seek back to the beginning (or set the Position property to 0) before you can use the stream further unless you want to append data to the stream, of course.

Another button, labeled Panel Object, shows the textual representation of a specific component, the panel, passing the component to the WriteComponent method. The third button, Form in Executable File, performs a different operation. Instead of streaming an existing object in memory, it loads in a TResourceStream object the design-time representation of the form that is, its DFM file from the corresponding resource embedded in the executable file:

procedure TFormText.btnResourceClick(Sender: TObject);

var

ResStr: TResourceStream;

begin

ResStr := TResourceStream.Create(hInstance, 'TFORMTEXT', RT_RCDATA);

try

ConvertAndShow (ResStr);

finally

ResStr.Free end;

end;

By clicking the buttons in sequence (or modifying the form of the program) you can compare the form saved in the

DFM file to the current run-time object.

Writing a Custom Stream Class

Besides using the existing stream classes, Delphi programmers can write their own stream classes and use them in place of the existing ones. To accomplish this, you need only specify how a generic block of raw data is saved and loaded, and the VCL will be able to use your new class wherever you call for it. You may not need to create a brand-new stream class to work with a new type of media, but only need to customize an existing stream. In that case, all you have to do is write the proper read and write methods.

As an example, I created a class to encode and decode a generic file stream. Although this example is limited by its use of a totally dumb encoding mechanism, it fully integrates with the VCL and works properly. The new stream class simply declares the two core reading and writing methods and has a property that stores a key:

type

TEncodedStream = class (TFileStream) private

FKey: Char;

public

constructor Create(const FileName: string; Mode: Word);

function Read(var Buffer; Count: Longint): Longint; override;

function Write(const Buffer; Count: Longint): Longint; override;

property Key: Char read FKey write FKey;

end;

The value of the key is added to each of the bytes saved to a file and subtracted when the data is read. Here is the complete code of the Write and Read methods, which uses pointers quite heavily:

constructor TEncodedStream.Create( const FileName: string; Mode: Word);

begin

inherited Create (FileName, Mode);

FKey := 'A'; // default end;

function TEncodedStream.Write(const Buffer; Count: Longint): Longint;

var

pBuf, pEnc: PChar;

I, EncVal: Integer;

begin

// allocate memory for the encoded buffer GetMem (pEnc, Count);

function TEncodedStream.Read(var Buffer; Count: Longint): Longint;

var

pBuf, pEnc: PChar;

I, CountRead, EncVal: Integer;

begin

// allocate memory for the encoded buffer GetMem (pEnc, Count);

try

// read the encoded buffer from the file CountRead := inherited Read (pEnc^, Count);

// use the output buffer as a string

// return the number of characters read Result := CountRead;

end;

The comments in this rather complex code should help you understand the details.

Now I used this encoded stream in a demo program, called EncDemo. The form of this program has two memo components and three buttons, as you can see in the following graphic:

The first button loads a plain text file in the first memo; the second button saves the text of this first memo in an encoded file; and the third button reloads the encoded file into the second memo, decoding it. In this example, after encoding the file, I've reloaded it in the first memo as a plain text file on the left, which of course is unreadable.

Because the encoded stream class is available, the code of this program is very similar to that of any other program using streams. For example, here is the method used to save the encoded file (you can compare its code to that of earlier examples based on streams):

EncStr := TEncodedStream.Create(SaveDialog1.Filename, fmCreate);

try

Memo1.Lines.SaveToStream (EncStr);

A new feature of Delphi 7 is official support for the ZLib compression library (available and described at

www.gzip.org/zlib). A unit interfacing ZLib has been available for a long time on Delphi's CD, but now it is included in the core distribution and is part of the VCL source (the ZLib and ZLibConst units). In addition to providing an interface to the library (which is a C library you can directly embed in the Delphi program, with no need to distribute a DLL), Delphi 7 defines a couple of helper stream classes: TCompressStream and TDecompressStream.

As an example of using these classes, I've written a small program called ZCompress that compresses and decompresses files. The program has two edit boxes in which you enter the name of the file to compress and the name of the resulting file, which is created if it doesn't already exist. When you click the Compress button, the source file is used to create the destination file; clicking the Decompress button moves the compressed file back to a

memory stream. In both cases, the result of the compression or decompression is displayed in a memo. Figure 4.6 shows the result for the compressed file (which happens to be the source code of the form of the current program).

Figure 4.6: The ZCompress example can compress a file using the ZLib library.

To make the code of this program more reusable, I've written two functions for compressing or decompressing a stream into another stream. Here is the code:

procedure CompressStream (aSource, aTarget: TStream);

comprStream.CopyFrom(aSource, aSource.Size);

comprStream.CompressionRate;

procedure DecompressStream (aSource, aTarget: TStream) ; var

decompStream: TDecompressionStream;

nRead: Integer;

Buffer: array [0..1023] of Char;

begin

Dans le document The Structure of the Book (Page 173-178)

Documents relatifs