|
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方法调用了BDEAPI的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调用了BDEAPI的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对象的属性和方法。
|