Unit SuperStream

SuperStream is copyright (c) 1998 Ross Judson. Email support@soletta.com for support.

All rights reserved. Unauthorized duplication or use prohibited. SuperStream documentation is in DelphiDoc format. For information on DelphiDoc, please visit www.soletta.com. Any updates to SuperStream can also be found there.

The SuperStream brings several important new capabilities to Delphi.

First, the TStreamAdapter class permits the construction of streams that alter or buffer the data that passes through them, on the way to another stream. An ownership concept is present, so that the streams are easy to build and free. This enables, for example, easy buffering of TFileStreams. The stock TFileStream is unbuffered and rather slow if many small io operations are made to it. A TBufferedStream adapter can be placed on top of it to improve performance. Stream adapters can be chained, so a TObjStream can be placed over a TBufferedStream, which is over a TFileStream, and so on.

TBufferedStreams speed up io against the underlying stream if it is slow for many small reads and writes.

TObjStream permits the easy storage and recovery of complex object graphs, complete with versioning. It makes use of as much information as it can that is provided by the Delphi compiler. Usage of the object streams is as simple as declaring an "object io procedure" for a class of object and registering it. TObjStream differs from most object streaming code in that it makes heavy use of Delphi's open arrays to make coding as easy as possible.

TObjStream understands class hierarchies, so any io procedures that are declared for superclasses are also called.

TObjStream is suitable for many lightweight object storage tasks. Couple TObjStream objects together with TBufferedStreams to improve performance.

Here are the steps to use an object stream:

  • Decide which classes should be persistent.
  • Write IO procedures for those classes. Each IO procedure should have the following signature: TObjIO = procedure(obj : TObject; stream : TObjStream; direction : TObjIODirection; version : Integer; var callSuper : Boolean);
  • Register the IO procedures by calling TObjStream.RegisterClass.
  • Create an object stream then read or write to it using WriteObject or ReadObject.
  • Your IO procedure should be prepared to receive a version number other than the tip revision. If it receives an older version, it should correctly load the older version of the object. The best way to do this is to use case statements, switching on the object version. When you write objects, you should generally write the latest version. By doing this, your application can read in old objects, but will automatically upgrade them to the latest version. See the TestIO routine in the sample file for an example of this.

    If you wish to register a class for IO but don't need to provide a procedure for it 'cause the superclass procedure will do, you still need to call RegisterClass. Pass nil in place of an IO procedure pointer. See the StrTestFrm.pas file for example IO procedures. You will primarily be using the TransferItems and TransferItemsEx calls. If you need to handle TDateTime, Single, or Double, make sure you use the TransferItemsEx call, passing the ssvt constants appropriate to your data.

    To transfer arrays of items, use the TransferArrays calls.

    Serializing sets can be a bit tricky. Use the TransferBlocks call to handle sets -- pass the address of the set and do a SizeOf() call on the set to find out how much data to store.

    Note that you can freely mix calls to WriteObject and the various transfer calls during your IO procedures.

    Here is an example IO procedure:

    
     procedure TestIO(obj : TObject; stream : TObjStream; direction : TObjIODirection; version : Integer; var callSuper : Boolean);
     begin
     with obj as TTest do
     case version of
     1:
     begin
     // old version didn't have a t value
     stream.TransferItems([s], [@s], direction, version);
     t := 'yipe';
     end;
     2:
     stream.TransferItems([s,t], [@s, @t], direction, version);
     end;
    
     end;
     

    Classes

    TBufferedInputStream - The buffered input stream adapter can accelerate the use of underlying streams.
    TBufferedOutputStream - The buffered output stream adapter can accelerate the use of underlying streams.
    TObjList - TObjList is a subclass of TList that contains a list of objects.
    TObjStream - Object stream adapters read and write objects from other streams.
    TObjStreamException - Exceptions thrown by the object streaming system will be of this class or a descendent.
    TStreamAdapter - TStreamAdapter defines a stream that wraps another stream.
    TStreamRegistration - Stream registration objects keep information about each class that is streamable.

    Functions

    Types

    TInitializer
    TObjCreation
    TObjIO
    TObjIODirection
    TObjStreamHeader
    TObjStreamOption
    TObjStreamOptions

    Constants

    ssvtAnsiString
    ssvtBoolean
    ssvtChar
    ssvtClass
    ssvtCurrency
    ssvtDateTime
    ssvtDouble
    ssvtExtended
    ssvtInteger
    ssvtInterface
    ssvtNone
    ssvtObject
    ssvtPChar
    ssvtPointer
    ssvtPWideChar
    ssvtSingle
    ssvtString
    ssvtVariant
    ssvtWideChar
    ssvtWideString

    Variables


    Functions


    Types


    TInitializer = procedure

    TObjCreation = procedure(obj : TObject; stream : TObjStream; version : Integer) of object

    TObjIO = procedure(obj : TObject; stream : TObjStream; direction : TObjIODirection; version : Integer; var callSuper : Boolean)
    IO procedures must have this signature. obj is the object being read or written. If being written, you will probably want to case the object to the correct type. If being read, the object will already have been created, but will NOT have had a constructor called. If your object requires that a constructor be called, invoke it directly, as in obj.Create. This will not create a new object, but will initialize yours. Note that many constructors just initialize variables -- if you're about to read in all those variables, you don't need to set them beforehand.

    Stream is the object stream. You may invoke any of its methods in your IO procedure, including WriteObject and the TransferXXX family.

    Direction indicates whether the call is for reading (iodirRead) or writing (iodirWrite). Most of the time you won't have to worry about this -- the TransferXXX calls read and write objects automatically depending on the direction flag passed to them.

    Version is the version of the object. You will always be requested to write only the latest version of an object, unless you specifically try to write an earlier version yourself. SuperStream won't do it. You may be asked to read an earlier version of an object. You should make sure you correctly read the earlier version, and fill in any extra information that isn't covered. That way you'll have automatic upgrading of your objects.

    CallSuper is a boolean that's preset to true, indicating that the superClass' IO procedure will be called. If you don't want the superClass' IO procedure to be called, set this to false before returning.


    TObjIODirection = (iodirRead, iodirWrite);
    This determines the whether the io is read or write.
    TObjStreamHeader = record
    magic : Integer;
    dummy1 : Integer;
    dummy2 : Integer;
    dummy3 : Integer;
    dummy4 : Integer;
    end;
    Each object stream starts with one of these.
    TObjStreamOption = (
      	osoGraph // support object graphs
      );
    Options that can be applied to object streams. Currently only the graph option is supported. Supplying osoGraph as an option permits the reading and writing of arbitrary graphs of objects. If this option is not supplied, objects will be written in full each time they are encountered. It is highly recommended that the osoGraph option be supplied if there is any chance of an object appearing more than once. Not supplying the option will result in a small speedup.
    TObjStreamOptions = set of TObjStreamOption
    A set of stream options.

    Constants

    ssvtAnsiString = vtAnsiString

    Indicate that the item is an AnsiString (long string).

    ssvtBoolean = vtBoolean

    Indicate that the item is a boolean.

    ssvtChar = vtChar

    Indicate that the item is a character.

    ssvtClass = vtClass

    Indicate that the item is a class object.

    ssvtCurrency = vtCurrency

    Indicate that the item is a currency value (like extended).

    ssvtDateTime = ssvtDouble

    Indicates that the item is a TDateTime, which is really a double.

    ssvtDouble = -3

    Indicates that the item is a double-precision (8 byte) floating point number.

    ssvtExtended = vtExtended

    Indicate that the item is an extended floating point value. Unless your variable is specifically declared as extended, you will rarely want to use this. Instead, use the ssvtSingle or ssvtDouble as appropriate.

    ssvtInteger = vtInteger

    Indicate that the item is an integer (32-bit).

    ssvtInterface = vtInterface

    Indicate that the item is an interface (not supported).

    ssvtNone = -1

    ssvtObject = vtObject

    Indicate that the item is an object.

    ssvtPChar = vtPChar

    Indicate that the item is a pointer to character.

    ssvtPointer = vtPointer

    Indicate that the item is a pointer.

    ssvtPWideChar = vtPWideChar

    Indicate that the item is a pointer to wide characters.

    ssvtSingle = -2

    Indicates that the item is a single-precision (4 byte) floating point number.

    ssvtString = vtString

    Indicate that the item is a short string.

    ssvtVariant = vtVariant

    Indicate that the item is a variant (not supported).

    ssvtWideChar = vtWideChar

    Indicate that the item is a wide character.

    ssvtWideString = vtWideString

    Indicate that the item is a wide string (not supported).

    Variables