後一頁 前一頁 回目錄 回首頁 |
20.1.6 TResourceStream 物件TResourceStream物件是另一類MemoryStream物件,它提供對Windows 應用程式資源的存取,因此稱它為資源流。TResourceSream也是從TCustomMemoryStream 繼承的。因此在TCustomMemoryStream物件的基礎上,定義了與指定資源模群組或資源文件建立連接的構造方法,並且還覆蓋了Write,以實現向資源文件中寫數據。 下面介紹TResourceStream的實現 1. 私有欄位 TResourceStream沒有定義新的屬性,但它在private部分定義了兩個數據欄位HResInfo和HGlobol和一個私有方法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物件內部使用的。它的構造方法Create和CreateFromID都是呼叫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. 構造方法Create和CreateFromID 這兩個方法在實現上沒有大的不同。顧名思義,第一個方法是通過資源名構造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方法 TResourceStream的Write方法只完成一件事,就產生這個異常事件,其實現如下: 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物件提供了修改TBlobField、TBytesField或TVarBytesField中數據的技術。開發者可以象對待文件或流那樣在資料庫欄位中讀寫數據。 傳統資料庫發展的一個重要趨向是往多媒體資料庫發展。目前比較??名和流行的資料庫都支援多媒體功能,多媒體數據存儲中的一大難點是數據結構不規則,數據量大。各大資料庫產品是採用BLOB技術解決多媒體數據存儲中的問題。Delphi的TBlobStream物件的意義就在於:一方面可以使Delphi應用程式充分利用多媒體資料庫的數據管理能力;另一方面又能利用Object Pascal的強大程式設計能力給多媒體資料庫提供全方向的功能擴展餘地。 使用TBlobStream物件可以在多媒體資料庫的BLOB欄位存儲任意格式的數據。一般說來,許多多媒體資料庫只能支援圖像、語音或者OLE伺服器支援的數據。利用TBlobStream則不同,只要是程式能夠定義的數據格式,它都能在BLOB欄位中讀寫,而不需要其它輔助工具。 TBlobStream用構造方法Create建立資料庫欄位和BLOB流的聯接。用Read或Write 方法存取和改變欄位中的內容;用Seek方法,在欄位中定位;用Truncate方法解除欄位中目前位置起所有的數據。
20.1.7.1 TBlobStream的屬性和方法
TBlobStream物件從TStream直接繼承,沒有增添新的屬性。它覆蓋了Read、Write 和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。函數返回新的指標位置值。當Origin為0(SoFromBegin)時,Offset的值必須大於等於零; 當Origin的值為2(SoFromEnd),Offset的值必須小於等於零。 4. Truncate方法 聲明:procedure Truncate; Truncate方法復原TBlobField、TBytesField或TVarBytesField中從目前位置起的數據。 5. Create方法 聲明:constructor Create(Field: TBlobField; Mode: TBlobStreamMode); Create方法使用Field參數建立BLOB流與BLOB欄位的聯接。Mode 的值可為bmRead、bmWrite和bmReadWrite。
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 部件。FRecord和FBuffer都是BLOB流內部使用的緩衝區,用於存儲FField所在記錄的數據,該數據記錄中不包含BLOB數據,TBlobStream使用FRecord作為呼叫BDE API函數的參數值。FFieldNo代表BLOB欄位的欄位號,也用於BDE API的參數傳遞,FOpened和FMocified都是狀態資訊,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參數給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 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 API的DbiPutBlob函數實現往資料庫BLOB欄位存儲數據。 該函數的各參數含義如下:表20.2 呼叫函數DbiPutBlob的各傳入參數的含義 ────────────────────────────── 參數名 含義 —————————————————————————————— FDataSetHandle 寫入的資料庫的BDE句柄 FRecord 寫入數據的BLOB欄位所在的記錄 FFieldNo BLOB 欄位號 FPosition 寫入的起始位置Count 寫入的數據的位元群組數 Buffer 所寫入的數據佔有的記憶體位址 ──────────────────────────────
方法中還根據 FField和FTransliterate的值判斷是否進行相應的字元集轉換,最後移動BLOB流的位置指標,並將修改標誌FModified置為True。3. Seek 和GetBlobSize方法的實現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 API的DbiGetBlobSize函數,該函數的參數的含義同前面的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流的目前位置起解除所有數據,並設定修改標誌FModified為True。在Delphi VCL中許多部件特別是資料庫應用方面的部件都用BDE API函數完成對資料庫的存取,如Data Access和Data 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物件是文件讀寫的基礎物件,在應用程式中使用的主要是TReader和TWriter。TReader和TWriter物件都直接從TFiler物件繼承。TFiler物件定義了Filer物件的基本屬性和方法。Filer物件主要完成兩大功能: ● 存取窗體文件和窗體文件中的部件● 提供數據緩衝,加快數據讀寫操作
20.2.1 TFiler 物件TFiler物件是TReader和TWriter的抽象類,定義了用於部件存儲的基本屬性和方法。它定義了Root屬性,Root指明了所讀或寫的部件的根物件,它的Create方法將Stream物件作為傳入參數以建立與Stream物件的聯繫, Filer物件的具體讀寫操作都是由Stream物件完成。因此,只要是Stream物件所能存取的媒介都能由Filer物件存取部件。TFiler 物件還提供了兩個定義屬性的方法:DefineProperty和DefineBinaryProperty,這兩個方法使物件能讀寫不在部件published部分定義的屬性。 因為 Filer物件主要用於存取Delphi的窗體文件和窗體文件中的部件,所以要清楚地理解Filer物件就要清楚Delphi 窗體文件(DFM文件)的結構。DFM文件是用於Delphi存儲窗體的。窗體是Delphi可視化程式設計的核心。窗體對應Delphi應用程式中的視窗,窗體中的可視部件對應視窗中的介面元素,非可視部件如TTable和TOpenDialog,對應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物件的屬性和方法。 |
後一頁 前一頁 回目錄 回首頁 |