Using Streams in Delphi

Streams are classes that let you read and write data. They provide a common interface for reading and writing to different media such as memory, strings, sockets, and BLOB fields in databases.

There are several stream classes, which all descend from TStream. Each stream class is specific to one media type. For example, TMemoryStream reads from or writes to a memory image; TFileStream reads from or writes to a file.

Stream classes all share several methods for reading and writing data. These methods are distinguished by whether they:

  • Return the number of bytes read or written.
  • Require the number of bytes to be known.
  • Raise an exception on error.

The Read method reads a specified number of bytes from the stream, starting at its current Position, into a buffer. Read then advances the current position by the number of bytes actually transferred. The prototype for Read is:

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

Read is useful when the number of bytes in the file is not known. Read returns the number of bytes actually transferred, which may be less than Count if the stream did not contain Count bytes of data past the current position. The Write method writes Count bytes from a buffer to the stream, starting at the current Position.

The prototype for Write is:

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

After writing to the file, Write advances the current position by the number bytes written, and returns the number of bytes actually written, which may be less than Count if the end of the buffer is encountered or the stream can’t accept any more bytes.

The counterpart procedures are ReadBuffer and WriteBuffer which, unlike Read and Write, do not return the number of bytes read or written. These procedures are useful in cases where the number of bytes is known and required, for example when reading in structures.

ReadBuffer and WriteBuffer raise an exception (EReadError and EWriteError) if the byte count can not be matched exactly. This is in contrast to the Read and Write methods, which can return a byte count that differs from the requested value. The prototypes for ReadBuffer and WriteBuffer are:

procedure ReadBuffer(var Buffer; Count: Longint); procedure WriteBuffer(const Buffer; Count: Longint);

These methods call the Read and Write methods to perform the actual reading and writing.

Reading and writing components

TStream defines specialized methods, ReadComponent and WriteComponent, for reading and writing components. You can use them in your applications as a way to save components and their properties when you create or alter them at runtime.

ReadComponent and WriteComponent are the methods that the IDE uses to read components from or write them to form files. When streaming components to or from a form file, stream classes work with the TFiler classes, TReader and TWriter, to read objects from the form file or write them out to disk.

Reading and writing strings

If you are passing a string to a read or write function, you need to be aware of the correct syntax. The Buffer parameters for the read and write routines are var and const types, respectively. These are untyped parameters, so the routine takes the address of a variable.

The most commonly used type when working with strings is a long string. However, passing a long string as the Buffer parameter does not produce the correct result. Long strings contain a size, a reference count, and a pointer to the characters in the string.

Consequently, dereferencing a long string does not result in the pointer element. You need to first cast the string to a Pointer or PChar, and then dereference it.

Copying data from one stream to another

When copying data from one stream to another, you do not need to explicitly read and then write the data. Instead, you can use the CopyFrom method.

Specifying the stream position and size

In addition to methods for reading and writing, streams permit applications to seek to an arbitrary position in the stream or change the size of the stream. Once you seek to a specified position, the next read or write operation starts reading from or writing to the stream at that position.

The Seek method is the most general mechanism for moving to a particular position in the stream. There are two overloads for the Seek method:

function Seek(Offset: Longint; Origin: Word): Longint; function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;

Both overloads work the same way. The difference is that one version uses a 32-bit integer to represent positions and offsets, while the other uses a 64-bit integer.

The Origin parameter indicates how to interpret the Offset parameter. Origin should be one of the following values:

  • soFromBeginning - Offset is from the beginning of the resource. Seek moves to the position Offset. Offset must be >= 0.
  • soFromCurrent - Offset is from the current position in the resource. Seek moves to Position + Offset.
  • soFromEnd - Offset is from the end of the resource. Offset must be <= 0 to indicate a number of bytes before the end of the file.

Seek resets the current stream position, moving it by the indicated offset. Seek returns the new current position in the stream.

Using Position and Size properties

All streams have properties that hold the current position and size of the stream. These are used by the Seek method, as well as all the methods that read from or write to the stream. The Position property indicates the current offset, in bytes, into the stream (from the beginning of the streamed data). The declaration for Position is:

property Position: Int64;

The Size property indicates the size of the stream in bytes. It can be used to determine the number of bytes available for reading, or to truncate the data in the stream. The declaration for Size is:

property Size: Int64;

Size is used internally by routines that read and write to and from the stream.

Setting the Size property changes the size of the data in the stream. For example, on a file stream, setting Size inserts an end of file marker to truncate the file. If the Size of the stream cannot be changed, an exception is raised. For example, trying to change the Size of a read-only file stream raises an exception.