STREAMS - TP 6.0/TPW 1.0 unit to supplement TurboVision/ObjectWindows streams Copyright D.J. Murdoch (1992). DESCRIPTION "Wierd Stream Tricks" might be a good name for this unit. It contains various doodads that I've written in the past, and a few new things, all on a theme of adding functionality to the streams in Borland's Turbo Pascal libraries TurboVision and ObjectWindows. LICENSE This unit is *not* public domain code. You may use it for non-profit purposes at no charge if credit is granted to me, but written permission must be obtained from me for use of this unit in commercial products. This is the first release of the STREAMS unit. There are probably bugs; I would really appreciate reports of any, or other suggestions for improvement. Please send either one to me at one of the following addresses: dmurdoch@watstat.waterloo.edu (Internet) 71631,122 (Compuserve) DJ Murdoch at Fidonet node 1:221/177.40 D. J. Murdoch 79 John St. W. Waterloo, Ontario, Canada N2L 1B7 SUMMARY Hierarchy TStream (from Objects) TFilter Base type for filters TEncryptFilter Encrypts as it writes; decrypts as it reads TLZWFilter Compresses as it writes; expands as it reads TTextFilter Provides text file interface to stream TLogFilter Provides logging of text file activity TRAMStream Stream in memory TDOSStream (from Objects) TBufStream (from Objects) TNamedBufStream Buffered file stream that knows its name TTempBufStream Buffered file stream that erases itself when done Procedures & functions: TempStream allocates a temporary stream OvrInitStream like OvrInitEMS, but buffers overlays on a stream May be called several times to buffer different segments on different streams. OvrDetachStream detaches stream from overlay system OvrDisposeStreams detaches all streams from overlay system and disposes of them OvrSizeNeeded Calculates the size needed to load the rest of the segments to a stream OvrLoadAll immediately copies as many overlay segments to the stream as will fit CONTENTS 1. TFilter a generic object to act as a filter to a stream 2. TEncryptFilter a filter which does simple encryption/decryption 3. TLZWFilter a filter which does LZW compression/decompression 4. TTextFilter a filter to provide the Read/ReadLn/Write/WriteLn interface to a stream 5. TLogFilter a filter to allow logging of activity on text files 6. TRAMStream a stream which resides entirely on the heap 7. TNamedBufStream a buffered file stream which knows its own name 8. TTempBufStream a temporary buffered file stream, which deletes itself when done 9. TempStream a procedure to allocate a temporary stream, in RAM, EMS, or on disk, according to a specified preference 10. OvrInitStream and related procedures procedures to allow overlays to be buffered on any stream or combination of streams 11. Miscellaneous constants and types DEMO PROGRAMS Encrypt.pas - encryption program, using TEncryptFilter Compress.pas - file compressor, using TLZWFilter Logdemo.pas - simple demo of TLogFilter Ovrdemo.pas - simple demo of multiple overlay files ovr1.pas - one overlaid unit ovr2.pas - a second overlaid unit 1. TFilter = object(TStream) "Filters" are programs which take a file as input and produce a new file as output. TFilter is an adaptation of this idea to streams. Every TFilter has a base stream, which can be any kind of stream. Every TFilter is itself a stream, which means you can read and write to it. When you do, it just relays your request to the base stream. One use of this is in the fact that you can make descendants of TFilter which massage the data on the way to and from the Base. Just override the Read and Write (and possibly other) methods, and you can make changes to the data flowing to or from *any* kind of stream. No need for special coding for an TEMSStream, or for a TDOSStream, or whatever. Your code can act on any of them. Examples of things to do are in the Streams unit: encryption (TEncryptFilter) and LZW compression (TLZWFilter). The other main use is to add other kinds of functionality to a stream. You can't use the formatting capabilities of ReadLn and WriteLn with standard streams, but if you use a TTextFilter (see below) you can. FIELDS Base : PStream; TFilter.Base holds the pointer to the base stream. StartOfs : longint; TFilter.StartOfs holds the offset in the base stream of offset 0 in the filter. This allows multiple filters to work on the same stream; one could work at offset 0, another at offset 1000, etc. Just position the base to the desired starting offset before initializing the filter. METHODS Constructor Init(ABase: PStream); TFilter.Init sets Base to point to ABase^, and sets StartOfs to the current position of the base stream. Destructor Done; virtual; If Base is not nil, then TFilter.Done disposes of the base stream before calling TStream.Done for itself. Function CheckStatus : boolean; virtual; Returns true if status is stOK. If it is, but the base status is not stOK, then it assumes that someone has called a Reset for the filter, so it calls Reset for the base stream. Borland should have made Reset virtual, and this kludge wouldn't have been necessary! Procedure CheckBase; Checks the base stream for an error. If Base^.status is not stOK, then it calls Error with Code=stBaseError and Info=Base^.status. Function GetPos : longint; virtual; Function GetSize : longint; virtual; Procedure Read(var buf; count : word); virtual; Procedure Seek(pos: longint); virtual; Procedure Truncate; virtual; Procedure Write(var buf; count: word); virtual; These methods all call the corresponding method in the base stream. Offsets are translated using StartOfs. Before the call, CheckStatus is called to propagate any Reset to the base; after the call, CheckBase is called to propagate any errors to the filter from the base. 2. TEncryptFilter = object(TFilter) This is a filter which does simple encryption/decryption on the base stream. The encryption method is to XOR each byte with a Random(256) value; the starting RandSeed is the key for the encryption. FIELD key : longint; The key is used as a Randseed replacement value. Randseed itself is left unmodified by Read and Write. METHODS Constructor Init(Akey:longint; ABase:PStream); The value AKey is used as the starting key for byte 0 of the filter. Anything read from ABase is decrypted using this key; anything written to it is encrypted. Procedure Read(var buf; count : word); virtual; Procedure Seek(pos : longint); virtual; Procedure Write(var buf; count: word); virtual; These methods all encrypt/decrypt and update the value of Key to correspond to the new position in the stream. The encryption method uses the same algorithm for encryption as for decryption. TEncryptFilter.Seek is fairly slow, because it updates the Key once for every byte difference between the current position and the new position. Moving backwards is slower, because the update algorithm is written in Pascal, not assembler as is the forward algorithm. 3. TLZWFilter = object(TFilter) This is a filter which does LZW compression as it writes to and decompression as it reads from the base stream. The LZW code is an adaptation of Wilbert van Leijen's implementation of the 12 bit LZW algorithm. It's quite fast, though not as fast as Wilbert's version; it gets slowed down a lot by having to save its state after every Read and Write operation. You're best off writing large chunks to it to take advantage of Wilbert's excellent code. (Historical note: Trying to rewrite this was what inspired me to write FULLDB, a program which allows source level debugging of external .ASM files. -djm) Each TLZWFilter takes over 28700 bytes of memory, because it keeps extensive tables to record the current state. One limitation of the TLZWFilter is that it can only be opened in read or write mode, and Seek cannot be used in write mode. FIELDS Mode : word; One of stOpenRead or stOpenWrite; the mode under which this filter was opened. Size : longint; The size of the expanded stream. This is the size that users of the filter will see; if compression is working, it will generally be bigger than Base^.GetSize. Position : longint; This is the position in the expanded stream. Tables : PLZWTables; This is a pointer to the tables used by the compression engine. They're automatically allocated on the heap. METHODS Constructor Init(ABase:PStream; AMode:TOpenMode); Allocates Tables from the heap, and opens the compressor in a mode of either stOpenRead or stOpenWrite. If reading, a signature and record of the uncompressed filesize is read from the base to confirm that it is compressed by LZW, and to prime the Tables. If writing, the signature is written to the stream. Destructor Done; virtual; Flushes all data to the stream, and writes the uncompressed filesize to the head of it before calling TFilter.done. Procedure Flush; virtual; Function GetPos: longint; virtual; Function GetSize:longint; virtual; Procedure Read(var buf; count:word); virtual; Procedure Seek(pos:longint); virtual; Procedure Truncate; virtual; Procedure Write(var buf; count: word); virtual; These methods all override the basic filter methods and compress/decompress the data. They check whether the operation requested can be performed in the current mode, and call Error with Code=stBadMode and Info=Mode if the operation is not supported. Seek is not supported at all in Write mode. In Read mode, it is slow for seeking forwards, and very slow for seeking backwards: it rewinds the file to the start and seeks forward from there by expanding everything. Truncate is not supported in either mode, and always causes a call to Error. Flush may only be called once; it changes Mode to 0, so any further operations will fail. 4. TTextFilter = object(TFilter) This is a filter which provides a Read/ReadLn/Write/WriteLn interface to a stream through its Textfile field. Once the filter is initialized, Textfile acts exactly like a text file in the standard I/O procedures, but reads come from the stream, and writes go to it. FIELD Textfile : Text; This is the dummy text file to use in Read/ReadLn/Write/WriteLn. METHODS Constructor Init(ABase:PStream; AName:String); Initializes the filter with base ABase. The name AName is stored as the name in the Textfile variable. Destructor Done; virtual; Closes the TextFile variable to flush any remaining data to the stream. 5. TLogFilter = object(TFilter) This filter allows logging of activity on text files to a stream. Logging is done very transparently. Once the TTextFilter is initialized, you call the Log method to start logging a particular text file, and the UnLog method to stop. When logging is in effect, any data read from or written to the text file is copied to the stream. Multiple files may be logged to the same stream. For example, you can log both Input and Output, and keep a record of an interactive session on the stream. It's also possible to log the same file to multiple streams. Just create the different TLogFilter objects, and call their Log methods with the same file as an argument. If you then call Unlog, you must do it in the *reverse* order to the order you called Log, e.g. S1^.log(output); S2^.log(output); S2^.unlog(output); S1^.unlog(output); is the correct order to log and unlog. One detail of the implementation may cause some trouble. The data is logged to the stream at the time it is written to disk by the text file. Since text files are buffered, this may not be the time at which you write to the text file, and multiple text files logged to the same stream may not have all data appearing in the correct order. FIELD LogList : ^Text; This is a pointer to the first text file currently being logged. METHODS Destructor done; virtual; Stops logging all text files by calling UnLog for each, and closes and disposes of the base stream. Procedure Log(var F:text); Starts logging the text file F. Continues until the UnLog method is called or the file is closed or Assign'd. Function UnLog(var F:text):boolean; Stops logging the text file F. Returns true if successful, false if not. Will fail if an Assign has been done to F, or F has been closed, or F has already been Unlogged, or another stream has started logging F and hasn't been UnLog'd yet. 6. TRAMStream = object(TStream) A stream which resides entirely on the heap. The maximum length is 65520 bytes. FIELDS cp : word; The current pointer for the stream. size : word; The current size of the stream. alloc : word; The size of the allocated block of memory. buffer : Pbyte_array; A pointer to the block of memory holding the stream data. METHODS Constructor init(Asize : word); Attempt to initialize the stream to a block size of Asize; initial stream size and position are 0. Destructor done; virtual; Dispose of the stream. Function getpos : longint; virtual; Function getsize : longint; virtual; Procedure read(var buf; count : word); virtual; Procedure seek(pos: longint); virtual; Procedure truncate; virtual; Procedure write(var buf; count: word); virtual; Implement the basic stream functions. 7. TNamedBufStream = object(TBufStream) A simple descendant of TBufStream which knows its own name. FIELD filename : PString { PChar in TPW }; The name of the stream. METHODS Constructor Init(name:FNameStr;mode:TOpenMode;abufsize:word); Open the file with the given name, and save the name. Destructor Done; virtual; Close the file. 8. TTempBufStream = object(TNamedBufStream) A temporary buffered file stream, which deletes itself when done. METHODS Constructor init(abufsize:word); Create a temporary file with a unique name, in the directory pointed to by the environment varable TEMP or in the current directory, and open it in read/write mode. Destructor done; virtual; Close and delete the temporary file. 9. Function TempStream(InitSize,MaxSize : longint; Preference:TStreamRanking):PStream; This procedure returns a pointer to a temporary stream from a choice of 3, specified in the Preference array. The first stream type listed in the Preference array which can be successfully created with the given sizes will be returned, or Nil if none can be made. ARGUMENTS Initsize : longint; The initial size to allocate to the stream. MaxSize : longint; The maximum size to which the stream should be allowed to grow. Preference : TStreamRanking; An array of 3 entries specifying what sort of temporary stream is desired. Supplied constants include: ForSpeed = (RAMStream, EMSStream, FileStream); ForSize = (FileStream,EMSStream, RAMStream); ForSizeInMem = (EMSStream, RAMStream, NoStream); ForOverlays = (EMSStream, FileStream,NoStream); 10. Stream overlay procedures These procedures allow overlays to be buffered on any stream or combination of streams. Some overlays can be loaded into EMS, others kept on disk, and others can be put onto any other available stream. PROCEDURES/FUNCTIONS Procedure OvrInitStream(S:PStream); Copies overlay segment code to S as new segments are loaded, and does reloads from there. You may call OvrInitStream multiple times, and different segments will be buffered on different streams, depending on the order in which they are loaded by the overlay loader. On the first call, an exit handler is installed which will call OvrDisposeStreams upon program termination. Procedure OvrDetachStream(BadS:PStream); Makes sure that the overlay system makes no references to BadS. Call this before disposing of a stream which has been passed to OvrInitStream, or you're very likely to crash. Procedure OvrDisposeStreams; Detaches and disposes of all streams being used by the overlay system. Function OvrSizeNeeded:longint; Returns the additional size required to load any segments which still haven't been loaded to a stream. Function OvrLoadAll:boolean; Forces all overlay segments to be copied into the stream; if successful (true) then no more references to the overlay file will be made. Warning: This function calls OvrClearBuf, so that any overlay files which are already in the regular overlay buffer will need to be reloaded. 11. Miscellaneous constants and types CONSTANTS stBadMode = 1; Error signalled when an operation is not permitted in the current mode. stStreamFail = 2; Error signalled when a stream Init failed. stBaseError = 3; Error signalled by a TFilter when the base stream has an error; the base stream's error number is put in the Info field. stMemError = 4; Not enough memory for operation. stSigError = 5; Problem with LZW file signature. BufSize : word = 2048; Buffer size to use when creating a buffered file stream in TempStream. TYPES TOpenMode = $3C00..$3DFF; This is the widest possible range of open modes for a TDOSStream descendant. Values outside this range can cause very serious bugs in programs, since the high byte is used as the DOS service number when the file is opened. PLZWTables = ^TLZWTables; TLZWTables = record ... These tables are used internally to maintain the state of a TLZWFilter. PByte_Array = ^TByte_Array; TByte_Array = array[0..65520] of byte; An array type used as a buffer in several places. TStreamType = (NoStream, RAMStream, EMSStream, FileStream); The types of streams that TempStream can create. TStreamRanking = array[1..NumTypes] of TStreamType; An array giving the order from most preferred to least preferred for a temporary stream.