程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> Delphi TStream詳解

Delphi TStream詳解

編輯:Delphi

這幾天為網友逆向一個圖片加密算法,結果算法容易,但是到寫代碼時寫得一肚子火,因為把文件讀寫的基本函數忘掉了。連流對象的幾個基本屬性和方法都忘了運用,浪費不少時間,後來還是上網搜索一翻,終於搞定。還是有必要把這篇文章收集起來。 Delphi TStream詳解   Stream對象,又稱流式對象,是TStream、THandleStream、TFileStream、TMemoryStream、TResourceStream和TBlobStream等的統稱。它們分別代表了在各種媒介上存儲數據的能力,它們將各種數據類型(包括對象和部件)  在內存、外存和數據庫字段中的管理操作抽象為對象方法,並且充分利用了面向對象技術的優點,應用程序可以相當容易地在各種Stream對象中拷貝數據。   下面介紹各種對象的數據和方法及使用方法。    TStream對象     TStream對象是能在各種媒介中存儲二進制數據的對象的抽象對象。從TStream 對象繼承的對象用於在內存、Windows資源文件、磁盤文件和數據庫字段等媒介中存儲數據。   Stream中定義了兩個屬性:Size和Position。它們分別以字節為單位表示的流的大小和當前指針位置。TStream中定義的方法用於在各種流中讀、寫和相互拷貝二進制數據。因為所有的Stream對象都是從TStream中繼承來的,所以在TStream中定義的域和方法都能被Stream對象調用和訪 問。此外,又由於面向對象技術的動態聯編功能,TStream為各種流的應用提供了統一的接口,簡化了流的使用;不同Stream對象是抽象了對不同存儲媒介的數據上的操作,因此,TStream的需方法為在不同媒介間的數據拷貝提供了最簡捷的手段。   TStream的屬性和方法     1. Position屬性  聲明:property Position: Longint;    Position屬性指明流中讀寫的當前偏移量。   2. Size屬性   聲明:property Size: Longint;  Size屬性指明了以字節為單位的流的的大小,它是只讀的。   3. CopyFrom方法   聲明:function CopyFrom(Source: TStream; Count: Longint): Longint;  CopyFrom從Source所指定的流中拷貝Count個字節到當前流中, 並將指針從當前位置移動Count個字節數,函數返回值是實際拷貝的字節數。   4. Read方法   聲明:function Read(var Buffer; Count: Longint): Longint; virtual; abstract;  Read方法從當前流中的當前位置起將Count個字節的內容復制到Buffer中,並把當前指針向後移動Count個字節數,函數返回值是實際讀的字節數。如果返回值小於Count,這意味著讀操作在讀滿所需字節數前指針已經到達了流的尾部。   Read方法是抽象方法。每個後繼Stream對象都要根據自己特有的有關特定存儲媒介的讀操作覆蓋該方法。而且流的所有其它的讀數據的方法(如:ReadBuffer,ReadComponent等)在完成實際的讀操作時都調用了Read方法。面向對象的動態聯編的優點就體現在這兒。因為後繼Stream對 象只需覆蓋Read方法,而其它讀操作(如ReadBuffer、ReadComponent等)都不需要重新定義,而且TStream還提供了統一的接口。   5. ReadBuffer方法   聲明:procedure ReadBuffer(var Buffer; Count: Longint);    ReadBuffer方法從流中將Count個字節復制到Buffer 中, 並將流的當前指針向後移動Count個字節。如讀操作超過流的尾部,ReadBuffer方法引起EReadError異常事件。   6. ReadComponent方法   聲明:function ReadComponent(Instance: TComponent): TComponent;  ReadComponent方法從當前流中讀取由Instance所指定的部件,函數返回所讀的部件。ReadComponent在讀Instance及其擁有的所有對象時創建了一個Reader對象並調用它的ReadRootComponent方法。   如果Instance為nil,ReadComponent的方法基於流中描述的部件類型信息創建部件,並返回新創建的部件。   7. ReadComponentRes方法   聲明:function ReadComponentRes(Instance: TComponent): TComponent;  ReadComponentRes方法從流中讀取Instance指定的部件,但是流的當前位置必須是由WriteComponentRes方法所寫入的部件的位置。   ReadComponentRes  首先調用ReadResHeader方法從流中讀取資源頭,然後調用ReadComponent方法讀取Instance。如果流的當前位置不包含一個資源頭。ReadResHeader將引發一個EInvalidImage異常事件。在Classes庫單元中也包含一個名為ReadComponentRes的函數,該函數執行相同的操作,只不過它基於應 用程序包含的資源建立自己的流。   8. ReadResHeader方法   聲明:procedure ReadResHeader;  ReadResHeader方法從流的當前位置讀取Windows資源文件頭,並將流的當前位置指針移到該文件頭的尾部。如果流不包含一個有效的資源文件頭,ReadResHeader將引發一個EInvalidImage異常事件。   流的ReadComponentRes方法在從資源文件中讀取部件之前,會自動調用ReadResHeader方法,因此,通常程序員通常不需要自己調用它。   9. Seek方法   聲明:function Seek(Offset: Longint; Origin: Word): Longint; virtual; abstract;  Seek方法將流的當前指針移動Offset個字節,字節移動的起點由Origin指定。如果Offset是負數,Seek方法將從所描述的起點往流的頭部移動。下表中列出了Origin的不同取值和它們的含義:   函數Seek的參數的取值  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━   常量       值      Seek的起點 Offset的取值 ─────────────────────────────────  SoFromBeginning 0  流的開頭 正 數  SoFromCurrent 1 流的當前位置 正數或負數   SoFromEnd 2 流的結尾 負 數  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━     10. Write方法   在Delphi對象式管理的對象中有兩類對象的方法都有稱為Write的:Stream對象和Filer對象。Stream對象的Write方法將數據寫進流中。Filer對象通過相關的流傳遞數據,在後文中會介紹這類方法。   Stream對象的Write方法聲明如下:   function Write(const Buffer; Count: Longint): Longint; virtual; abstract;    Write方法將Buffer中的Count個字節寫入流中,並將當前位置指針向流的尾部移動Count個字節,函數返回寫入的字節數。   TStream的Write方法是抽象的,每個繼承的Stream對象都要通過覆蓋該方法來提供向特定存儲媒介(內存、磁盤文件等)寫數據的特定方法。流的其它所有寫數據的方法(如WriteBuffer、WriteComponent)都調用Write擔當實際的寫操作。   11. WriteBuffer方法   聲明:procedure WriteBuffer(const Buffer; Count: Longint);    WriteBuffer的功能與Write相似。WriteBuffer方法調用Write來執行實際的寫操作,如果流沒能寫所有字節,WriteBuffer會觸發一個EWriteError異常事件。   12. WriteComponent方法   在Stream對象和Filer對象都有被稱為WriteComponent的方法。Stream對象的WriteComponent方法將Instance所指定的部件和它所包含的所有部件都寫入流中;Writer對象的WriteComponent將指定部件的屬性值寫入Writer對象的流中。   Stream對象的WriteComponent方法聲明是這樣的: procedure WriteComponent(Instance: Tcomponent);      WriteComponent創建一個Writer對象,並調用Writer的WriteRootComponent方法將Instance及其擁有的對象寫入流。   13. WriteComponentRes方法   聲明:WriteComponentRes(const ResName: String; Instance: TComponent);    WriteComponentRes方法首先往流中寫入標准Windows 資源文件頭,然後將Instance指定的部件寫入流中。要讀由WriteComponentRes寫入的部件,必須調用ReadComponentRes方法。   WriteComponentRes使用ResName傳入的字符串作為資源文件頭的資源名,然後調用WriteComponent方法將Instance和它擁有的部件寫入流。   14. WriteDescendant方法   聲明:procedure WriteDescendant(Instance Ancestor: TComponent);    Stream對象的WriteDescendant方法創建一個Writer對象,然後調入該對象的WriteDescendant方法將Instance部件寫入流中。Instance可以是從Ancestor部件繼承的窗體,也可以是在從祖先窗體中繼承的窗體中相應於祖先窗體中Ancestor部件的部件。   15. WriteDescendantRes方法   聲明:procedure WriteDescendantRes(const ResName: String; Instance, Ancestor: TComponent);   WriteDescendantRes方法將Windows資源文件頭寫入流,並使用ResName作用資源名,然後調用WriteDescendant方法,將Instance寫入流。   TStream的實現原理     TStream對象是Stream對象的基礎類,這是Stream對象的基礎。為了能在不同媒介上的存儲數據對象,後繼的Stream對象主要是在Read和Write方法上做了改進,。因此,了解TStream是掌握Stream對象管理的核心。Borland公司雖然提供了Stream對象的接口說明文檔,但對於其實現和應 用方法卻沒有提及,筆者是從Borland Delphi 2.0 Client/Server Suite 提供的源代碼和部分例子程序中掌握了流式對象技術。   下面就從TStream的屬性和方法的實現開始。   1. TStream屬性的實現   前面介紹過,TStream具有Position和Size兩個屬性,作為抽象數據類型,它抽象了在各種存儲媒介中讀寫數據所需要經常訪問的域。那麼它們是怎樣實現的呢?   在自定義部件編寫這一章中介紹過部件屬性定義中的讀寫控制。Position和Size也作了讀寫控制。定義如下:   property Position: Longint read GetPosition write SetPosition; property Size: Longint read GetSize;     由上可知,Position是可讀寫屬性,而Size是只讀的。   Position屬性的實現就體現在GetPosition和SetPosition。當在程序運行過程中,任何讀取Position的值和給Position賦值的操作都會自動觸發私有方法GetPosition和SetPosition。兩個方法的聲明如下:   function TStream.GetPosition: Longint; begin Result := Seek(0, 1); end;   procedure TStream.SetPosition(Pos: Longint); begin Seek(Pos, 0); end;   在設置位置時,Delphi編譯機制會自動將Position傳為Pos。   前面介紹過Seek的使用方法,第一參數是移動偏移量,第二個參數是移動的起點,返回值是移動後的指針位置。   Size屬性的實現只有讀控制,完全屏蔽了寫操作。讀控制方法GetSize實現如下:   function TStream.GetSize: Longint; var Pos: Longint; begin Pos := Seek(0, 1); Result := Seek(0, 2); Seek(Pos, 0); end;   2. TStream方法的實現   ⑴ CopyFrom方法   CopyFrom是Stream對象中很有用的方法,它用於在不同存儲媒介中拷貝數據。例如,內存與外部文件之間、內存與數據庫字段之間等。它簡化了許多內存分配、文件打開和讀寫等的細節,將所有拷貝操作都統一到Stream對象上。   前面曾介紹:CopyFrom方法帶Source和Count兩個參數並返回長整型。該方法將Count個字節的內容從Source拷貝到當前流中,如果Count值為0則拷貝所有數據。   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; if Count > MaxBufSize then BufSize := MaxBufSize else BufSize := Count; GetMem(Buffer, BufSize); try while Count <> 0 do begin if Count > BufSize then  N := BufSize  else N := Count; Source.ReadBuffer(Buffer^, N); WriteBuffer(Buffer^, N); Dec(Count, N); end; finally FreeMem(Buffer, BufSize); end; end;     ⑵ ReadBuffer方法和WriteBuffer方法   ReadBuffer方法和WriteBuffer方法簡單地調用虛擬函數Read、Write來讀寫流中數據,它比Read和Write增加了讀寫數據出錯時的異常處理。   procedure TStream.ReadBuffer(var Buffer; Count: Longint); begin if (Count <> 0) and (Read(Buffer, Count) <> Count) then raise EReadError.CreateRes(SReadError); end;   procedure TStream.WriteBuffer(const Buffer; Count: Longint); begin if (Count <> 0) and (Write(Buffer, Count) <> Count) then raise EWriteError.CreateRes(SWriteError); end;     ⑶ ReadComponent、ReadResHeader和ReadComponentRes方法   ReadComponent方法從當前流中讀取部件。在實現上ReadComponent方法創建了一個TStream對象,並用TReader的ReadRootComponent方法讀部件。在Delphi對象式管理中,Stream對象和Filer對象結合很緊密。Stream對象的許多方法的實現需要Filer對象的支持,而Filer對象的構造函數 直接就以Stream對象為參數。在ReadComponent方法的實現中就可清楚地看到這一點:   function TStream.ReadComponent(Instance: TComponent): TComponent; var Reader: TReader; begin Reader := TReader.Create(Self, 4096); try Result := Reader.ReadRootComponent(Instance); finally Reader.Free; end; end;   ReadResHeader方法用於讀取Windows資源文件的文件頭,由ReadComponentRes方法在讀取Windows資源文件中的部件時調用,通常程序員不需自己調用。如果讀取的不是資源文件ReadResH := FSize + Offset; end; Result := FPosition; end;     Offse代表移動的偏移量。Origin代表移動的起點,值為0表示從文件頭開始,值為1表示從當前位置開始,值為2表示從文件尾往前,這時OffSet一般為負數。Seek的實現沒有越界的判斷。   3. SaveToStream和SaveToFile方法   SaveToStream方法是將MemoryStream對象中的內容寫入Stream所指定的流。其實現如下:   procedure TCustomMemoryStream.SaveToStream(Stream: TStream); begin if FSize <> 0 then Stream.WriteBuffer(FMemory^, FSize); end;     SaveToStream方法調用了Stream的WriteBuffer方法,直接將FMemory中的內容按FSize字節長度寫入流中。   SaveToFile方法是與SaveToStream方法相關的。SaveToFile方法首先創建了一個FileStream對象,然後把該文件Stream對象作為SaveToStream的參數,由SaveToStream 方法執行寫操作,其實現如下:   procedure TCustomMemoryStream.SaveToFile(const FileName: string); var Stream: TStream; begin Stream := TFileStream.Create(FileName, fmCreate); try SaveToStream(Stream); finally Stream.Free; end; end;     在Delphi 的許多對象的SaveToStream 和SaveToFile、LoadFromStream和LoadFromFile方法的實現都有類似的嵌套結構。   TMemoryStream對象      TMemoryStream對象是一個管理動態內存中的數據的Stream對象,它是從TCustomMemoryStream中繼承下來的,除了從TCustomMemoryStream中繼承的屬性和方法外,它還增加和覆蓋了一些用於從磁盤文件和其它注台讀數據的方法。它還提供了寫入、消除內存內容的動態內存管理方法。下面 介紹它的這些屬性和方法。   TMemoryStream的屬性和方法     1. Capacity屬性   聲明:property Copacity: Longint;  Capacity屬性決定了分配給內存流的內存池的大小。這與Size屬性有些不同。Size屬性是描述流中數據的大小。在程序中可以將Capacity 的值設置的比數據所需最大內存大一些,這樣可以避免頻繁地重新分配。   2. Realloc方法   聲明:function Realloc(var NewCapacity: Longint): Pointer; virtual;  Realloc方法,以8K為單位分配動態內存,內存的大小由NewCapacity指定,函數返回指向所分配內存的指針。   3. SetSize方法   SetSize方法消除內存流中包含的數據,並將內存流中內存池的大小設為Size字節。如果Size為零,是SetSize方法將釋放已有的內存池,並將Memory屬性置為nil;否則,SetSize方法將內存池大小調整為Size。 4. Clear方法   聲明:procedure Clear;  Clear方法釋放內存中的內存池,並將Memory屬性置為nil。在調用Clear方法後,Size和Position屬性都為0。   5. LoadFromStream方法   聲明:procedure LoadFromStream(Stream: TStream);  LoadFromStream方法將Stream指定的流中的全部內容復制到MemoryStream中,復制過程將取代已有內容,使MemoryStream成為Stream的一份拷貝。   6. LoadFromFile方法   聲明:procedure LoadFromFile(count FileName: String);  LoadFromFile方法將FileName指定文件的所有內容復制到MemoryStream中,並取代已有內容。調用LoadFromFile方法後,MemoryStream將成為文件內容在內存中的完整拷貝。   TMemoryStream對象的實現原理     TMemoryStream從TCustomMemoryStream對象直接繼承,因此可以享用TCustomMemoryStream的屬性和方法。前面講過,TCustomMemoryStream是用於內存中數據操作的抽象對象,它為MemoryStream對象的實現提供了框架,框架中的內容還要由具體MemoryStream對象去填充。TMemoryStrea m對象就是按動態內存管理的需要填充框架中的具體內容。下面介紹TMemoryStream對象的實? FBuffer := AllocMem(FDataSet.RecordSize); FRecord := FBuffer; if not FDataSet.GetCurrentRecord(FBuffer) then Exit; OpenMode := dbiReadOnly; end else begin if not (FDataSet.State in [dsEdit, dsInsert]) then DBError(SNotEditing); OpenMode := dbiReadWrite; end; Check(DbiOpenBlob(FDataSet.Handle, FRecord, FFieldNo, OpenMode)); end; FOpened := True; if Mode = bmWrite then Truncate; end;    該方法首先是用傳入的Field參數給FField,FDataSet,FRecord和FFieldNo賦值。方法中用AllocMem按當前記錄大小分配內存,並將指針賦給FBuffer,用DataSet部件的GetCurrentRecord方法,將記錄的值賦給FBuffer,但不包括BLOB數據。   方法中用到的DbiOpenBlob函數是BDE的API函數,該函數用於打開數據庫中的BLOB字段。   最後如果方法傳入的Mode參數值為bmWrite,就調用Truncate將當前位置指針以後的 數據刪除。   分析這段源程序不難知道:   ● 讀寫BLOB字段,不允許BLOB字段所在DataSet部件有Filter,否則產生異常事件   ● 要讀寫BLOB字段,必須將DataSet設為編輯或插入狀態   ● 如果BLOB字段中的數據作了修改,則在創建BLOB 流時,不再重新調用DBiOpenBlob函數,而只是簡單地將FOpened置為True,這樣可以用多個BLOB 流對同一個BLOB字段讀寫     Destroy方法釋放BLOB字段和為FBuffer分配的緩沖區,其實現如下:   destructor TBlobStream.Destroy; begin if FOpened then begin if FModified then FField.FModified := True; if not FField.FModified then DbiFreeBlob(FDataSet.Handle, FRecord, FFieldNo); end; if FBuffer <> nil then FreeMem(FBuffer, FDataSet.RecordSize); if FModified then try FField.DataChanged; except Application.HandleException(Self); end; end;     如果BLOB流中的數據作了修改,就將FField的FModified置為True;如果FField的Modified為False就釋放BLOB字段,如果FBuffer不為空,則釋放臨時內存。最後根據FModified的值來決定是否啟動FField的事件處理過程DataChanged。   不難看出,如果BLOB字段作了修改就不釋放BLOB字段,並且對BLOB 字段的修改只有到Destroy時才提交,這是因為讀寫BLOB字段時都避開了FField,而直接調用BDE API函數。這一點是在應用BDE API編程中很重要,即一定要修改相應數據庫部件的狀態。   2. Read和Write方法的實現   Read和Write方法都調用BDE API函數完成數據庫BLOB字段的讀寫,其實現如下:    function TBlobStream.Read(var Buffer; Count: Longint): Longint; var Status: DBIResult; begin Result := 0; if FOpened then begin Status := DbiGetBlob(FDataSet.Handle, FRecord, FFieldNo, FPosition, Count, @Buffer, Result); case Status of DBIERR_NONE, DBIERR_ENDOFBLOB: begin if FField.FTransliterate then NativeToAnsiBuf(FDataSet.Locale, @Buffer, @Buffer, Result); Inc(FPosition, Result); end; DBIERR_INVALIDBLOBOFFSET: {Nothing}; else DbiError(Status); end; end; end;     Read方法使用了BDE  API的DbiGetBlob函數從FDataSet中讀取數據,在本函數中,各參數的含義是這樣的:FDataSet.Handle代表DataSet的BDE句柄,FReacord表示BLOB字段所在記錄,FFieldNo表示BLOB字段號,FPosition表示要讀的的數據的起始位置,Count表示要讀的字節數,Buffer是讀出數據所占的內存, Result是實際讀出的字節數。該BDE函數返回函數調用的錯誤狀態信息。   Read方法還調用了NativeToAnsiBuf進行字符集的轉換。   function TBlobStream.Write(const Buffer; Count: Longint): Longint; var Temp: Pointer; begin Result := 0; if FOpened then begin if FField.FTransliterate then begin www.2cto.com GetMem(Temp, Count); try AnsiToNativeBuf(FDataSet.Locale, @Buffer, Temp, Count); Check(DbiPutBlob(FDataSet.Handle, FRecord, FFieldNo, FPosition, Count, Temp)); finally FreeMem(Temp, Count); end; end else Check(DbiPutBlob(FDataSet.Handle, FRecord, FFieldNo, FPosition, Count, @Buffer)); Inc(FPosition, Count); Result := Count; FModified := True; end; end;  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved