Author: Peter Johnson
In Java there are various predefined stream classes that provide filters for other
stream classes - the filter classes essentially "wrap" the streams they operate on.
The filters can often be applied to further filters. This article demonstrates how
we can do this in Delphi in a way that is extendable - i.e. we can wrap filters
around other filters.
Answer:
First of all, let's look at why we want to do this. Well say you want to write some
data primitives as text to a stream and the text to be formatted to fit on a page,
word wrapping properly. Then if we can wrap a filter that formats the primitives
around another that formats the text and this filter is wrapped round a file stream
object, then all we have to do is access the methods of the first class and the
rest of the process happens automatically.
The approach I've taken is to define a class, TStreamWrapper, that provides a base
class for any filters that we want to define. Any TStreamWrapper performs it's i/o
using another TStream object - the wrapped object. The key point is that
TStreamWrapper is itself derived from TStream, so that it can also wrap other
TSteamWrapper objects - giving the extensibility we need. TStreamWrapper can also
cause a wrapped stream to be freed when it is itself freed - allowing the wrapped
streams to be created "on the fly" when the TStreamWrapper constructor is called.
There is no additional functionality built in to TStreamWrapper - this is to be
provided by derived classes. A small example class is demonstrated here.
First to TStreamWrapper. Here's the class declaration:
1 type
2 TStreamWrapper = class(TStream)
3 private
4 FBaseStream: TStream; {The "wrapped" stream}
5 FCloseStream: Boolean; {Free wrapped stream on destruction?}
6 protected
7 procedure SetSize(NewSize: Longint); override;
8 {Sets the size of the stream to the given value if the operation is
9 supported by the underlying stream}
10 property BaseStream: TStream read FBaseStream;
11 {Gives access to the underlying stream to descended classes}
12 public
13 constructor Create(const Stream: TStream;
14 const CloseStream: Boolean = False); virtual;
15 {If CloseStream is true the given underlying stream is freed when
16 this object is freed}
17 destructor Destroy; override;
18 // Implementation of abstract methods of TStream
19 function read(var Buffer; Count: Longint): Longint; override;
20 function write(const Buffer; Count: Longint): Longint; override;
21 function Seek(Offset: Longint; Origin: Word): Longint; override;
22 end;
23
24 //and the implementation is:
25
26 constructor TStreamWrapper.Create(const Stream: TStream;
27 const CloseStream: Boolean);
28 begin
29 inherited Create;
30 // Record wrapped stream and if we free it on destruction
31 FBaseStream := Stream;
32 FCloseStream := CloseStream;
33 end;
34
35 destructor TStreamWrapper.Destroy;
36 begin
37 // Close wrapped stream if required
38 if FCloseStream then
39 FBaseStream.Free;
40 inherited Destroy;
41 end;
42
43 function TStreamWrapper.read(var Buffer; Count: Integer): Longint;
44 begin
45 // Simply call underlying stream's Read method
46 Result := FBaseStream.read(Buffer, Count);
47 end;
48
49 function TStreamWrapper.Seek(Offset: Integer; Origin: Word): Longint;
50 begin
51 // Simply call the same method in the wrapped stream
52 Result := FBaseStream.Seek(Offset, Origin);
53 end;
54
55 procedure TStreamWrapper.SetSize(NewSize: Integer);
56 begin
57 // Set the size property of the wrapped stream
58 FBaseStream.Size := NewSize;
59 end;
60
61 function TStreamWrapper.write(const Buffer; Count: Integer): Longint;
62 begin
63 // Simply call the same method in the wrapped stream
64 Result := FBaseStream.write(Buffer, Count);
65 end;
66
67 //We can now derive a small filter class - TStrStream. As it stands it's not
68 particularly useful, but does demostrate the techniques. The class reads writes
69 strings (which are preceded by their lengths) to any stream. The declaration is:
70
71 type
72 TStrStream = class(TStreamWrapper)
73 public
74 procedure WriteString(AString: string);
75 function ReadString: string;
76 end;
77
78 //The class is implemented as follows:
79
80 function TStrStream.ReadString: string;
81 var
82 StrLen: Integer; // the length of the string
83 PBuf: PChar; // buffer to hold the string that is read
84 begin
85 // Get length of string (as 32 bit integer)
86 ReadBuffer(StrLen, SizeOf(Integer));
87 // Now get string
88 // allocate enough memory to hold string
89 GetMem(PBuf, StrLen);
90 try
91 // read chars into buffer and set resulting string
92 ReadBuffer(PBuf^, StrLen);
93 SetString(Result, PBuf, StrLen);
94 finally
95 // deallocate buffer
96 FreeMem(PBuf, StrLen);
97 end;
98 end;
99
100 procedure TStrStream.WriteString(AString: string);
101 var
102 Len: Integer; // length of string
103 begin
104 // Write out length of string as 32 bit integer
105 Len := Length(AString);
106 WriteBuffer(Len, SizeOf(Integer));
107 // Now write out the string's characters
108 WriteBuffer(PChar(AString)^, Len);
109 end;
110
111 //The following code should demonstrate how to write a string to a file and read it
112 back in again. Here we use a file stream that is created on the fly and
113 automatically closed when we are done. of course you could create the stream and
114 close it separately.
115
116 procedure WriteText(const Txt: string);
117 var
118 TS: TStrStream;
119 begin
120 // This opens stream on a file stream that will be closed when this stream closes
121 TS := TStrStream.Create(TFileStream.Create('test.dat', fmCreate), True);
122 TS.WriteString(Txt);
123 TS.Free; // this closes wrapped file stream
124 end;
125
126 function ReadText: string;
127 var
128 TS: TStrStream;
129 begin
130 TS := TStrStream.Create(TFileStream.Create('test.dat', fmOpenRead), True);
131 Result := TS.ReadString;
132 TS.Free;
133 end;
The filter in this example provides additional methods to those in TStreamWrapper.
We can also provide filters that override the Read and Write methods to alter the
way that files are written. My resource file classes (available for download from
my website) use this method to allow data to be written to RCDATA resource files -
the classes take care of maintaining the correct file structure.
Component Download: http://www.delphidabbler.com/download.php?file=streamwrapdemo.ziphttp://www.delphidabbler.com/download.php?file=streamwrapdemo.zip
|