後一頁
前一頁
回目錄
回首頁
第二十章 開發Delphi物件式數據管理功能(二)

20.1.6 TResourceStream物件 

  TResourceStream物件是另一類MemoryStream物件,它提供對Windows 應用程式資源的存取,因此稱它為資源流。TResourceSream也是從TCustomMemoryStream 繼承的。因此在TCustomMemoryStream物件的基礎上,定義了與指定資源模群組或資源文件建立連接的構造方法,並且還覆蓋了Write,以實現向資源文件中寫數據。

下面介紹TResourceStream的實現

  1. 私有欄位

  TResourceStream沒有定義新的屬性,但它在private部分定義了兩個數據欄位HResInfoHGlobol和一個私有方法Initialize,它們的定義如下:

 

TResourceStream = class(TCustomMemoryStream)

private

HResInfo: HRSRC;

HGlobal: THandle;

procedure Initialize(Instance: THandle; Name, ResType: PChar);

end;

 

  HRSRC是描述Windows資源資訊的結構句柄。HGlobal變數代表資源所在模群組的句柄。如果操作的是應用程式資源,HGlohal就代表EXE程式的句柄;如果是動態鍊結庫(DLL),則HGlobal 代表動態鍊結庫的句柄。TResourceStream物件使用這兩上變數存取應用程式或動態鍊結庫中的資源。

  Initialize方法是TResourceStream物件內部使用的。它的構造方法CreateCreateFromID都是呼叫Initialize方法完成對TResourceStream的初始化。它的實現如下:

 

procedure TResourceStream.Initialize(Instance: THandle; Name, ResType: PChar);

 

procedure Error;

begin

raise EResNotFound.Create(FmtLoadStr(SResNotFound, [Name]));

end;

 

begin

HResInfo := FindResource(Instance, Name, ResType);

if HResInfo = 0 then Error;

HGlobal := LoadResource(Instance, HResInfo);

if HGlobal = 0 then Error;

SetPointer(LockResource(HGlobal), SizeOfResource(Instance, HResInfo));

end;

 

 該方法實現中,首先呼叫Windows函數FoundResource得到由參數Instance指定的模群組中的名為Name和型式為ResType的資源,然後呼叫LoadResource將資源呼叫記憶體,並返回該資源在記憶體中的句柄,最後,將該資源復制到ResourceStream中。方法的Instance參數代表要呼叫的資源所在的模群組句柄。模群組可以是可執行文件,也可以是動態鍊結庫。如果在讀取資源時沒在模群組中發現要找的資源則產生異常事件。

  2. 構造方法CreateCreateFromID

  這兩個方法在實現上沒有大的不同。顧名思義,第一個方法是通過資源名構造TResourceStream; 第二個方法通過資源ID構造TResourceStream,而且在實現過程中,它們都呼叫了Initialize方法。下面是它們的實現:

 

constructor TResourceStream.Create(Instance: THandle; const ResName: string;

ResType: PChar);

begin

inherited Create;

Initialize(Instance, PChar(ResName), ResType);

end;

 

constructor TResourceStream.CreateFromID(Instance: THandle; ResID: Integer;

ResType: PChar);

begin

inherited Create;

Initialize(Instance, PChar(ResID), ResType);

end;

 

  這兩個方法中都有Instance參數,該參數值的含義在Insitialize中介紹過。

  3. Write方法

  TResourceStreamWrite方法只完成一件事,就產生這個異常事件,其實現如下:

 

function TResourceStream.Write(const Buffer; Count: Longint): Longint;

begin

raise EStreamError.CreateRes(SCantWriteResourceStreamError);

end;

 

  從方法實現中可以看到,TSourceStream物件是不允許寫數據的。一旦往資源流中寫數據將產生異常事件。

  4. 析構方法Destroy

  該方法產生給資源解鎖,然後釋放該資源,最後呼叫繼承的Destroy方法釋放ResourceStream。其實現如下:

 

destructor TResourceStream.Destroy;

begin

UnlockResource(HGlobal);

FreeResource(HResInfo);

inherited Destroy;

end;

 

  回顧Initialize方法,我們不難發現:

  ● ResourceStream沒有額外地給資源重新分配記憶體,而是直接使用HGlobal句柄所指 的記憶體欄位

  ● ResourceStream中的資源在流的生存期,始終是Lock狀態,因此要根據Windows 的記憶體使用規則合理安排ResourceStream的使用

  ● ResourceStream只是用於存取應用程式和動態鍊結庫中的資源的

 

Classes在單元中提供了InternalReadComponentRes函數,該函數使用了TResourceStream物件從Delphi應用程式中讀取部件。Delphi是將窗體和部件資訊放在模群組資源的RCDATA段的。

 

20.1.7 TBlobStream物件

 

  從Delphi 資料庫開發基台這個意義上說,TBlobStream 物件是個很重要的物件。TBlobStream物件提供了修改TBlobFieldTBytesFieldTVarBytesField中數據的技術。開發者可以象對待文件或流那樣在資料庫欄位中讀寫數據。

  傳統資料庫發展的一個重要趨向是往多媒體資料庫發展。目前比較??名和流行的資料庫都支援多媒體功能,多媒體數據存儲中的一大難點是數據結構不規則,數據量大。各大資料庫產品是採用BLOB技術解決多媒體數據存儲中的問題。DelphiTBlobStream物件的意義就在於:一方面可以使Delphi應用程式充分利用多媒體資料庫的數據管理能力;另一方面又能利用Object Pascal的強大程式設計能力給多媒體資料庫提供全方向的功能擴展餘地。

  使用TBlobStream物件可以在多媒體資料庫的BLOB欄位存儲任意格式的數據。一般說來,許多多媒體資料庫只能支援圖像、語音或者OLE伺服器支援的數據。利用TBlobStream則不同,只要是程式能夠定義的數據格式,它都能在BLOB欄位中讀寫,而不需要其它輔助工具。

  TBlobStream用構造方法Create建立資料庫欄位和BLOB流的聯接。用ReadWrite 方法存取和改變欄位中的內容;用Seek方法,在欄位中定位;用Truncate方法解除欄位中目前位置起所有的數據。

 

20.1.7.1 TBlobStream的屬性和方法

 

  TBlobStream物件從TStream直接繼承,沒有增添新的屬性。它覆蓋了ReadWrite Seek方法,提供了對BLOB欄位的存取操作;它增添了Truncate方法以實現BLOB欄位中的解除操作。

  1. Read方法

  聲明:function Read(var Buffer; Count: Longint): Longint;

Read方法從資料庫欄位的目前位置起復制Count個位元群組的內容到Buffer中。Buffer也必須至少分配Count個位元群組。Read方法返回實際傳輸的位元群組數,因為傳輸的位元群組數可能小於Count,所以需要選擇符的邊界判斷。

  2. Write方法

  聲明:function Write(const Buffer; Count: Longint); override; Longint;

Write方法從Buffer中向資料庫欄位的目前位置復制Count個位元群組的內容。Buffer必須分配有Count個位元群組的記憶體空間,函數返回實際傳輸的位元群組數,傳輸過程也要進行選擇符邊界判斷。

  3. Seek方法

  聲明:function Seek(Offset: Longint; Origin: Word): Longint;

  Seek方法重新設定BLOB流中的指標位置。如果Origin的值是soFromBeginning,則新的指標位置是Offset; Origin的值是soFromCurrent,則新的指標位置是Position+Offset;如果Origin的值是SoFromCurrent,則新的指標位置是Size+Offset。函數返回新的指標位置值。當Origin0(SoFromBegin)時,Offset的值必須大於等於零; Origin的值為2(SoFromEnd)Offset的值必須小於等於零。

  4. Truncate方法

  聲明:procedure Truncate;

Truncate方法復原TBlobFieldTBytesFieldTVarBytesField中從目前位置起的數據。

  5. Create方法

  聲明:constructor Create(Field: TBlobField; Mode: TBlobStreamMode);

  Create方法使用Field參數建立BLOB流與BLOB欄位的聯接。Mode 的值可為bmReadbmWritebmReadWrite

 

20.1.7.2 TBlobStream的實現原理

 

  敘述TBlobStream物件的實現原理,不可避免地要涉及它的私有欄位,下面是私有欄位的定義:

 

TBlobStream = class(TStream)

private

FField: TBlobField;

FDataSet: TDataSet;

FRecord: PChar;

FBuffer: PChar;

FFieldNo: Integer;

FOpened: Boolean;

FModified: Boolean;

FPosition: Longint;

public

end;

 

  FField是與BLOB流相聯的資料庫BLOB欄位,該欄位用於BLOB流的內部存取。FDataSet是代表FField所在的資料庫,它可以是TTable部件,也可以是TQuery 部件。FRecordFBuffer都是BLOB流內部使用的緩衝區,用於存儲FField所在記錄的數據,該數據記錄中不包含BLOB數據,TBlobStream使用FRecord作為呼叫BDE API函數的參數值。FFieldNo代表BLOB欄位的欄位號,也用於BDE API的參數傳遞,FOpenedFMocified都是狀態資訊,FPosition表示BLOB流的目前位置,下面介紹TBlobStream方法實現。

  1. Create方法和Destroy方法的實現

  Create方法的功能主要是建立BlobStream流與BLOB欄位的聯繫並初始化某些私有變數。其實現如下:

  

constructor TBlobStream.Create(Field: TBlobField; Mode: TBlobStreamMode);

var

OpenMode: DbiOpenMode;

begin

FField := Field;

FDataSet := Field.DataSet;

FRecord := FDataSet.ActiveBuffer;

FFieldNo := Field.FieldNo;

if FDataSet.State = dsFilter then

DBErrorFmt(SNoFieldAccess, [FField.DisplayName]);

if not FField.FModified then

begin

if Mode = bmRead then

begin

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參數給FFieldFDataSetFRecordFFieldNo賦值。方法中用AllocMem按目前記錄大小分配記憶體,並將指標賦給FBuffer,用DataSet部件的GetCurrentRecord方法,將記錄的值賦給FBuffer,但不包括BLOB數據。

  方法中用到的DbiOpenBlob函數是BDEAPI函數,該函數用於打開資料庫中的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流中的數據作了修改,就將FFieldFModified置為True;如果FFieldModifiedFalse就釋放BLOB欄位,如果FBuffer不為空,則釋放臨時記憶體。最後根據FModified的值來決定是否啟動FField的事件處理過程DataChanged

  不難看出,如果BLOB欄位作了修改就不釋放BLOB欄位,並且對BLOB 欄位的修改只有到Destroy時才提交,這是因為讀寫BLOB欄位時都避開了FField,而直接呼叫BDE API函數。這一點是在應用BDE API程式設計中很重要,即一定要修改相應資料庫部件的狀態。

  2. ReadWrite方法的實現

  ReadWrite方法都呼叫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 APIDbiGetBlob函數從FDataSet中讀取數據,在本函數中,各參數的含義是這樣的:FDataSet.Handle代表DataSetBDE句柄,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

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;

 

Write方法呼叫了BDE APIDbiPutBlob函數實現往資料庫BLOB欄位存儲數據。

該函數的各參數含義如下:

 

20.2 呼叫函數DbiPutBlob的各傳入參數的含義

 ──────────────────────────────

   參數名           含義

——————————————————————————————

  FDataSetHandle 寫入的資料庫的BDE句柄

  FRecord 寫入數據的BLOB欄位所在的記錄

FFieldNo BLOB欄位號

  FPosition 寫入的起始位置

  Count 寫入的數據的位元群組數

  Buffer 所寫入的數據佔有的記憶體位址

  ──────────────────────────────

 

  方法中還根據FFieldFTransliterate的值判斷是否進行相應的字元集轉換,最後移動BLOB流的位置指標,並將修改標誌FModified置為True

3. SeekGetBlobSize方法的實現

  Seek方法的功能主要是移動BLOB流的位置指標。GetBlobSize方法是私有的,在Seek方法中被呼叫,其功能是得到BLOB數據的大小。它們的實現如下:

 

function TBlobStream.GetBlobSize: Longint;

begin

Result := 0;

if FOpened then

Check(DbiGetBlobSize(FDataSet.Handle, FRecord, FFieldNo, Result));

end;

 

function TBlobStream.Seek(Offset: Longint; Origin: Word): Longint;

begin

case Origin of

0: FPosition := Offset;

1: Inc(FPosition, Offset);

2: FPosition := GetBlobSize + Offset;

end;

Result := FPosition;

end;

 

GetBlobSize呼叫了BDE APIDbiGetBlobSize函數,該函數的參數的含義同前面的API函數相同。

  4. Truncate方法

該方法是通過呼叫BDE API函數實現的。其實現如下:

 

procedure TBlobStream.Truncate;

begin

if FOpened then

begin

Check(DbiTruncateBlob(FDataSet.Handle, FRecord, FFieldNo, FPosition));

FModified := True;

end;

end;

 

  該方法從BLOB流的目前位置起解除所有數據,並設定修改標誌FModifiedTrue。在Delphi VCL中許多部件特別是資料庫應用方面的部件都用BDE API函數完成對資料庫的存取,如Data AccessData Control部件。各種資料庫部件都是BDE API函數外層的包裝簡化了對資料庫的存取操作。BDE API中還提供了避開BDE組態工具在程式中直接處理Alias(建立、修改、解除等)的函數支援,這也是部件所沒有提供的。在Delphi資料庫應用裝設程式中,這些Alias操作函數無疑是相當重要的。有關BDE API函數的詳細介紹,可閱讀Delphi2.0 Client/Server Suite所帶的BDE API 輔助敘述文件。

 

 

20.2 讀寫物件的實現原理和應用

 

  讀寫物件(Filer)包括TFiler物件、TReader物件和TWriter物件。TFiler物件是文件讀寫的基礎物件,在應用程式中使用的主要是TReaderTWriterTReaderTWriter物件都直接從TFiler物件繼承。TFiler物件定義了Filer物件的基本屬性和方法。

  Filer物件主要完成兩大功能:

  ● 存取窗體文件和窗體文件中的部件

  ● 提供數據緩衝,加快數據讀寫操作

 

20.2.1 TFiler物件

 

 TFiler物件是TReaderTWriter的抽象類,定義了用於部件存儲的基本屬性和方法。它定義了Root屬性,Root指明了所讀或寫的部件的根物件,它的Create方法將Stream物件作為傳入參數以建立與Stream物件的聯繫, Filer物件的具體讀寫操作都是由Stream物件完成。因此,只要是Stream物件所能存取的媒介都能由Filer物件存取部件。TFiler 物件還提供了兩個定義屬性的方法:DefinePropertyDefineBinaryProperty,這兩個方法使物件能讀寫不在部件published部分定義的屬性。

  因為Filer物件主要用於存取Delphi的窗體文件和窗體文件中的部件,所以要清楚地理解Filer物件就要清楚Delphi 窗體文件(DFM文件)的結構。

  DFM文件是用於Delphi存儲窗體的。窗體是Delphi可視化程式設計的核心。窗體對應Delphi應用程式中的視窗,窗體中的可視部件對應視窗中的介面元素,非可視部件如TTableTOpenDialog,對應Delphi應用程式的某項功能。Delphi應用程式的設計實際上是以窗體的設計為中心。因此,DFM文件在Delphi應用設計中也佔很重要的位置。窗體中的所有元素包括窗體自身的屬性都包含在DFM文件中。

  在Delphi應用程式視窗,介面元素是按擁有關係相互聯繫的,因此樹狀結構是最自然的表達形式;相應地,窗體中的部件也是按樹狀結構群群組織;對應在DFM文件中,也要表達這種關係。DFM文件在物理上,是以二進制方式存儲的,在邏輯上則是以樹狀結構安排各部件的關係。Delphi編輯視窗支援以文本方式顯示DFM文件。從該文本中可以看清窗體的樹狀結構。下面是DFM文件的文本顯示:

 

  Object Form1: TForm1

Left = 72

Top = 77

ActiveControl = DBIMage1

Object Panell: TPanel

Left = 6

Object DBLabel1: TDBText

end

Object DBImage1: TDBImage

end

end

Object Panel2: TPanel

Left = 6

Object Label1: TLable

end

end

Object Panel3: TPanel

Left = 6

Object DBLabel2: TDBText

end

end

end

  關於DFM文件中存儲屬性值的規則,請參見自定義部件開發這一章。

  對照TFiler物件的屬性。Root屬性就表示部件樹的根——窗體。Filer物件的許多方法都是讀從根起始的樹中所有的部件。Ancestor屬性表示根的祖先物件,IgnoreChildren屬性則是讀部件時忽略根的子結點。

  下面介紹Filer物件的屬性和方法。


後一頁
前一頁
回目錄
回首頁