Delphi Programming Guide
Delphi Programmer 

Menu  Table of contents

Part I - Foundations
  Chapter 1 – Delphi 7 and Its IDE
  Chapter 2 – The Delphi Programming Language
  Chapter 3 – The Run-Time Library
  Chapter 4 – Core Library classes
  Chapter 5 – Visual Controls
  Chapter 6 – Building the User Interface
  Chapter 7 – Working with Forms
Part II - Delphi Object-Oriented Architectures
  Chapter 8 – The Architecture of Delphi Applications
  Chapter 9 – Writing Delphi Components
  Chapter 10 – Libraries and Packages
  Chapter 11 – Modeling and OOP Programming (with ModelMaker)
  Chapter 12 – From COM to COM+
Part III - Delphi Database-Oriented Architectures
  Chapter 13 – Delphi's Database Architecture
  Chapter 14 – Client/Server with dbExpress
  Chapter 15 – Working with ADO
  Chapter 16 – Multitier DataSnap Applications
  Chapter 17 – Writing Database Components
  Chapter 18 – Reporting with Rave
Part IV - Delphi, the Internet, and a .NET Preview
  Chapter 19 – Internet Programming: Sockets and Indy
  Chapter 20 – Web Programming with WebBroker and WebSnap
  Chapter 21 – Web Programming with IntraWeb
  Chapter 22 – Using XML Technologies
  Chapter 23 – Web Services and SOAP
  Chapter 24 – The Microsoft .NET Architecture from the Delphi Perspective
  Chapter 25 – Delphi for .NET Preview: The Language and the RTL
       
  Appendix A – Extra Delphi Tools by the Author
  Appendix B – Extra Delphi Tools from Other Sources
  Appendix C – Free Companion Books on Delphi
       
  Index    
  List of Figures    
  List of tables    
  List of Listings    
  List of Sidebars  

 
Previous Section Next Section

Streaming

Another core area of the Delphi class library is its support for streaming, which includes file management, memory, sockets, and other sources of information arranged in a sequence. The idea of streaming is that you move through the data while reading it, much like the Read and Write functions traditionally used by the Pascal language (and discussed in Chapter 12 of Essential Pascal (see Appendix C for availability of this e-book).

The TStream Class

The VCL defines the abstract TStream class and several subclasses. The parent class, TStream, has just a few properties, and you'll never create an instance of it, but it has an interesting list of methods you'll generally use when working with derived stream classes.

The TStream class defines two properties, Size and Position. All stream objects have a specific size (which generally grows if you write something after the end of the stream), and you must specify a position within the stream where you want to either read or write information.

Reading and writing bytes depends on the actual stream class you are using, but in both cases you don't need to know much more than the size of the stream and your relative position in the stream to read or write data. In fact, that's one of the advantages of using streams. The basic interface remains the same whether you're manipulating a disk file, a binary large object (BLOB) field, or a long sequence of bytes in memory.

In addition to the Size and Position properties, the TStream class also defines several important methods, most of which are virtual and abstract. (In other words, the TStream class doesn't define what these methods do; therefore, derived classes are responsible for implementing them.) Some of these methods are important only in the context of reading or writing components within a stream (for instance, ReadComponent and WriteComponent), but some are useful in other contexts, too. In Listing 4.2, you can find the declaration of the TStream class, extracted from the Classes unit.

Listing 4.2: The Public Portion of the Definition of the TStream Class
Start example
TStream = class(TObject)
public
  // read and write a buffer
  function Read(var Buffer; Count: Longint): Longint; virtual; abstract;
  function Write(const Buffer; Count: Longint): Longint; virtual; abstract;
  procedure ReadBuffer(var Buffer; Count: Longint);
  procedure WriteBuffer(const Buffer; Count: Longint);
   
  // move to a specific position
  function Seek(Offset: Longint; Origin: Word): Longint; overload; virtual;
  function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
    overload; virtual;
   
  // copy the stream
  function CopyFrom(Source: TStream; Count: Int64): Int64;
   
  // read or write a component
  function ReadComponent(Instance: TComponent): TComponent;
  function ReadComponentRes(Instance: TComponent): TComponent;
  procedure WriteComponent(Instance: TComponent);
  procedure WriteComponentRes(const ResName: string; Instance: TComponent);
  procedure WriteDescendent(Instance, Ancestor: TComponent);
  procedure WriteDescendentRes(
    const ResName: string; Instance, Ancestor: TComponent);
  procedure WriteResourceHeader(const ResName: string; out FixupInfo: Integer);
  procedure FixupResourceHeader(FixupInfo: Integer);
  procedure ReadResHeader;
   
  // properties
  property Position: Int64 read GetPosition write SetPosition;
  property Size: Int64 read GetSize write SetSize64;
end;
End example

The basic use of a stream involves calling the ReadBuffer and WriteBuffer methods, which are very powerful but not terribly easy to use. The first parameter is an untyped buffer in which you can pass the variable to save from or load to. For example, you can save into a file a number (in binary format) and a string, with this code:

var
  stream: TStream;
  n: integer;
  str: string;
begin
  n := 10;
  str := 'test string';
  stream := TFileStream.Create ('c:\tmp\test', fmCreate);
  stream.WriteBuffer (n, sizeOf(integer));
  stream.WriteBuffer (str[1], Length (str));
  stream.Free;

An alternative approach is to let specific components save or load data to and from streams. Many VCL classes define a LoadFromStream or a SaveToStream method, including TStrings, TStringList, TBlobField, TMemoField, TIcon, and TBitmap.

Specific Stream Classes

Creating a TStream instance makes no sense, because this class is abstract and provides no direct support for saving data. Instead, you can use one of the derived classes to load data from or store it to an actual file, a BLOB field, a socket, or a memory block. Use TFileStream when you want to work with a file, passing the filename and some file access options to the Create method. Use TMemoryStream to manipulate a stream in memory and not an actual file.

Several units define TStream-derived classes. The Classes unit includes the following classes:

  • THandleStream defines a stream that manipulates a disk file represented by a file handle.

  • TFileStream defines a stream that manipulates a disk file (a file that exists on a local or network disk) represented by a filename. It inherits from THandleStream.

  • TCustomMemoryStream is the base class for streams stored in memory but is not used directly.

  • TMemoryStream defines a stream that manipulates a sequence of bytes in memory. It inherits from TCustomMemoryStream.

  • TStringStream provides a simple way to associate a stream to a string in memory, so that you can access the string with the TStream interface and also copy the string to and from another stream.

  • TResourceStream defines a stream that manipulates a sequence of bytes in memory, and provides read-only access to resource data linked into the executable file of an application (the DFM files are an example of this resource data). It inherits from TCustomMemoryStream.

Stream classes defined in other units include the following:

  • TBlobStream defines a stream that provides simple access to database BLOB fields. There are similar BLOB streams for database access technologies other than the BDE, including TSQLBlobStream and TClientBlobStream. (Notice that each type of dataset uses a specific stream class for BLOB fields.) All these classes inherit from TMemoryStream.

  • TOleStream defines a stream for reading and writing information over the interface for streaming provided by an OLE object.

  • TWinSocketStream provides streaming support for a socket connection.

Using File Streams

Creating and using a file stream can be as simple as creating a variable of a type that descends from TStream and calling components' methods to load content from the file:

var
  S: TFileStream;
begin
  if OpenDialog1.Execute then
  begin
    S := TFileStream.Create (OpenDialog1.FileName, fmOpenRead);
    try
      Memo1.Lines.LoadFromStream (S);
    finally
      S.Free;
    end;
  end;
end;

As you can see in this code, the Create method for file streams has two parameters: the name of the file and a flag indicating the requested access mode. In this case, you want to read the file, so you use the fmOpenRead flag (other available flags are documented in the Delphi help).

Note 

Of the different modes, the most important are fmShareDenyWrite, which you'll use when you're simply reading data from a shared file, and fmShareExclusive, which you'll use when you're writing data to a shared file. There is a third parameter in TFileStream.Create, called Rights. This parameter is used to pass file access permissions to the Linux filesystem when the access mode is fmCreate (that is, only when you are creating a new file). This parameter is ignored on Windows.

A big advantage of streams over other file access techniques is that they're very interchangeable, so you can work with memory streams and then save them to a file, or you can perform the opposite operations. This might be a way to improve the speed of a file-intensive program. Here is a snippet of a file-copying function to give you another idea of how you can use streams:

procedure CopyFile (SourceName, TargetName: String);
var
  Stream1, Stream2: TFileStream;
begin
  Stream1 := TFileStream.Create (SourceName, fmOpenRead);
  try
    Stream2 := TFileStream.Create (TargetName, fmOpenWrite or fmCreate);
    try
      Stream2.CopyFrom (Stream1, Stream1.Size);
    finally
      Stream2.Free;
    end
  finally
    Stream1.Free;
  end
end;

Another important use of streams is to handle database BLOB fields or other large fields directly. You can export such data to a stream or read it from one by calling the SaveToStream and LoadFromStream methods of the TBlobField class.

Note 

Delphi 7 streaming support adds a new exception base class, EFileStreamError. Its constructor takes as parameter a filename for error reporting. This class standardizes and largely simplifies the notification of file-related errors in streams.

The TReader and TWriter Classes

By themselves, the VCL stream classes don't provide much support for reading or writing data. In fact, stream classes don't implement much beyond simply reading and writing blocks of data. If you want to load or save specific data types in a stream (and don't want to perform a great deal of typecasting), you can use the TReader and TWriter classes, which derive from the generic TFiler class.

Basically, the TReader and TWriter classes exist to simplify loading and saving stream data according to its type, and not just as a sequence of bytes. To do this, TWriter embeds special signatures into the stream that specify the type for each object's data. Conversely, the TReader class reads these signatures from the stream, creates the appropriate objects, and then initializes those objects using the subsequent data from the stream.

For example, I could have written out a number and a string to a stream by writing:

var
  stream: TStream;
  n: integer;
  str: string;
  w: TWriter;
begin
  n := 10;
  str := 'test string';
  stream := TFileStream.Create ('c:\tmp\test.txt', fmCreate);
  w := TWriter.Create (stream, 1024);
  w.WriteInteger (n);
  w.WriteString (str);
  w.Free;
  stream.Free;

This time the file will include the extra signature characters, so I can read back this file only by using a TReader object. For this reason, using TReader and TWriter is generally confined to component streaming and is seldom applied in general file management.

Streams and Persistency

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:

Click To expand
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
    MemStr.Free
  end;
end;
   
procedure TformText.ConvertAndShow (aStream: TStream);
var
  ConvStream: TStream;
begin
  aStream.Position := 0;
  ConvStream := TMemoryStream.Create;
  try
    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.

Compressing Streams with ZLib

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).

Click To expand
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);
var
  comprStream: TCompressionStream;
begin
  comprStream := TCompressionStream.Create(
    clFastest, aTarget);
  try
    comprStream.CopyFrom(aSource, aSource.Size);
    comprStream.CompressionRate;
  finally
    comprStream.Free;
  end;
end;
   
procedure DecompressStream (aSource, aTarget: TStream) ;
var
  decompStream: TDecompressionStream;
  nRead: Integer;
  Buffer: array [0..1023] of Char;
begin
  decompStream := TDecompressionStream.Create(aSource);
  try
    // aStreamDest.CopyFrom (decompStream, size) doesn't work
    // properly as you don't know the size in advance,
    // so I've used a similar "manual" code
    repeat
      nRead := decompStream.Read(Buffer, 1024);
      aTarget.Write (Buffer, nRead);
    until nRead = 0;
  finally
    decompStream.Free;
  end;
end;

As you can see in the code comment, the decompression operation is slightly more complex because you cannot use the CopyFrom method: You don't know the size of the resulting stream in advance. If you pass 0 to the method, it will try to get the size of the source stream, which is a TDecompressionStream. However, this operation causes an exception, because the compression and decompression streams can be read only from the beginning to the end and don't allow for seeking the end of the file.


 
Previous Section Next Section


 


 

Delphi Sources


Copyright © 2004-2024 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide
ร๐๓๏๏เ ยส๎ํ๒เ๊๒ๅ   Facebook   ั๑๛๋๊เ ํเ Twitter