TStream 是所有串流物件的基底抽象類別。後繼串流類別主要在 Read/Write 上改進以適應不同儲存媒介。掌握 TStream 是掌握串流物件管理的關鍵。Borland 雖提供介面文件,但未公開實作細節,筆者從 Delphi 2.0 Client/Server Suite 原始碼與範例程式中習得串流技術。
TStream 定義 Position 與 Size 兩個屬性,抽象化各種儲存媒介中頻繁存取的位置與大小資訊。屬性定義採讀寫控制:
property Position: Longint read GetPosition write SetPosition; property Size: Longint read GetSize;
Position 可讀寫,Size 唯讀。
function TStream.GetPosition: Longint;
begin
Result := Seek(0, 1); { soFromCurrent }
end;
procedure TStream.SetPosition(Pos: Longint);
begin
Seek(Pos, 0); { soFromBeginning }
end;
讀取位置呼叫 Seek(0,1) 取得目前指標;設定位置呼叫 Seek(Pos,0) 移至絕對位移。
function TStream.GetSize: Longint;
var
Pos: Longint;
begin
Pos := Seek(0, 1); { 記住目前位置 }
Result := Seek(0, 2); { 移至末尾取得大小 }
Seek(Pos, 0); { 還原位置 }
end;
透過暫時移動指標至末尾取得大小,再還原位置。
function TStream.CopyFrom(Source: TStream; Count: Longint): Longint;
const
MaxBufSize = $F000;
var
BufSize, N: Integer;
Buffer: PChar;
begin
if Count = 0 then
begin
Source.Position := 0;
Count := Source.Size;
end;
Result := Count;
GetMem(Buffer, MaxBufSize);
try
while Count > 0 do
begin
if Count > MaxBufSize then BufSize := MaxBufSize else BufSize := Count;
N := Source.Read(Buffer^, BufSize);
if N = 0 then Break;
Write(Buffer^, N);
Dec(Count, N);
end;
finally
FreeMem(Buffer, MaxBufSize);
end;
end;
CopyFrom 簡化記憶體配置、檔案開啟/讀寫細節,統一在串流物件上複製資料。參數 Source 來源串流,Count 複製位元組數(0 表全部),回傳實際複製位元組數。
function TStream.ReadComponent(Instance: TComponent): TComponent;
var
Reader: TReader;
begin
Reader := TReader.Create(Self, 4096);
try
if Instance = nil then
Result := Reader.ReadRootComponent(nil)
else
begin
Reader.ReadRootComponent(Instance);
Result := Instance;
end;
finally
Reader.Free;
end;
end;
建立 TReader 呼叫 ReadRootComponent。若 Instance=nil 依串流中類型資訊建立新元件。
ReadResHeader 讀取 Windows 資源檔頭,驗證有效性:
procedure TStream.ReadResHeader;
var
ReadCount: Longint;
Header: array[0..79] of Char;
begin
FillChar(Header, SizeOf(Header), 0);
ReadCount := Read(Header, SizeOf(Header) - 1);
if (Byte((@Header[0])^) = $FF) and (Word((@Header[1])^) = 10) then
Seek(StrLen(Header + 3) + 10 - ReadCount, 1)
else
raise EInvalidImage.CreateRes(SInvalidImage);
end;
ReadComponentRes 先呼叫 ReadResHeader 再呼叫 ReadComponent,用於從資源檔讀取元件。
procedure TStream.WriteComponent(Instance: TComponent);
begin
WriteDescendant(Instance, nil);
end;
procedure TStream.WriteDescendant(Instance, Ancestor: TComponent);
var
Writer: TWriter;
begin
Writer := TWriter.Create(Self, 4096);
try
Writer.WriteDescendant(Instance, Ancestor);
finally
Writer.Free;
end;
end;
建立 TWriter 呼叫 WriteDescendant。WriteComponent 直接呼叫 WriteDescendant(Instance, nil)。
procedure TStream.WriteComponentRes(const ResName: string; Instance: TComponent); begin WriteDescendantRes(ResName, Instance, nil); end; procedure TStream.WriteDescendantRes(const ResName: string; Instance, Ancestor: TComponent); var HeaderSize: Integer; Origin, ImageSize: Longint; Header: array[0..79] of Char; begin Byte((@Header[0])^) := $FF; Word((@Header[1])^) := 10; HeaderSize := StrLen(StrUpper(StrPLCopy(@Header[3], ResName, 63))) + 10; Word((@Header[HeaderSize - 6])^) := $1030; Longint((@Header[HeaderSize - 4])^) := 0; WriteBuffer(Header, HeaderSize); Origin := Position; WriteDescendant(Instance, Ancestor); ImageSize := Position - Origin; Position := Origin - 4; WriteBuffer(ImageSize, SizeOf(Longint)); Position := Origin + ImageSize; end;
寫入標準 Windows 資源檔頭(含資源名、類型標識),再寫入元件資料,最後回填資料大小。WriteComponentRes 與 ReadComponentRes 配合可讀寫 Delphi DFM 檔案,善用 Delphi 系統功能。