流式对象的实现原理和应用

流式对象的实现原理和应用

  Stream对象,又称流式对象,是TStreamTHandleStreamTFileStreamTMemoryStreamTResourceStreamTBlobStream等的统称。它们分别代表了在各种媒介上存储数据的能力,它们将各种数据类型(包括对象和部件) 在内存、外存和数据库字段中的管理操作抽象为对象方法,并且充分利用了面向对象技术的优点,应用程序可以相当容易地在各种Stream对象中拷贝数据。

  下面介绍各种对象的数据和方法及使用方法。

 

1.1 TStream对象

 

  TStream对象是能在各种媒介中存储二进制数据的对象的抽象对象。从TStream 对象继承的对象用于在内存、Windows资源文件、磁盘文件和数据库字段等媒介中存储数据。

  TStream中定义了两个属性:SizePosition。它们分别以字节为单位表示的流的大小和当前指针位置。TStream中定义的方法用于在各种流中读、写和相互拷贝二进制数据。因为所有的Stream对象都是从TStream中继承来的,所以在TStream中定义的域和方法都能被Stream对象调用和访问。此外,又由于面向对象技术的动态联编功能,TStream为各种流的应用提供了统一的接口,简化了流的使用;不同Stream对象是抽象了对不同存储媒介的数据上的操作,因此,TStream的需方法为在不同媒介间的数据拷贝提供了最简捷的手段。

 

1.1.1 TStream的属性和方法

 

  1. Position属性 

    声明:property Position: Longint;

  Position属性指明流中读写的当前偏移量。

  2. Size属性

  声明:property Size: Longint;

    Size属性指明了以字节为单位的流的的大小,它是只读的。

  3. CopyFrom方法

  声明:function CopyFrom(Source: TStream; Count: Longint): Longint;

    CopyFromSource所指定的流中拷贝Count个字节到当前流中, 并将指针从当前位置移动Count个字节数,函数返回值是实际拷贝的字节数。

  4. Read方法

  声明:function Read(var Buffer; Count: Longint): Longint; virtual; abstract;

    Read方法从当前流中的当前位置起将Count个字节的内容复制到Buffer中,并把当前指针向后移动Count个字节数,函数返回值是实际读的字节数。如果返回值小于Count,这意味着读操作在读满所需字节数前指针已经到达了流的尾部。

  Read方法是抽象方法。每个后继Stream对象都要根据自己特有的有关特定存储媒介的读操作覆盖该方法。而且流的所有其它的读数据的方法(如:ReadBufferReadComponent等)在完成实际的读操作时都调用了Read方法。面向对象的动态联编的优点就体现在这儿。因为后继Stream对象只需覆盖Read方法,而其它读操作(ReadBufferReadComponent)都不需要重新定义,而且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方法。

  如果InstancenilReadComponent的方法基于流中描述的部件类型信息创建部件,并返回新创建的部件。

  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的不同取值和它们的含义:

 

                    20.1  函数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个字节,函数返回写入的字节数。

   TStreamWrite方法是抽象的,每个继承的Stream对象都要通过覆盖该方法来提供向特定存储媒介(内存、磁盘文件等)写数据的特定方法。流的其它所有写数据的方法(WriteBufferWriteComponent)都调用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对象,并调用WriterWriteRootComponent方法将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写入流。

 

1.1.2 TStream的实现原理

 

  TStream对象是Stream对象的基础类,这是Stream对象的基础。为了能在不同媒介上的存储数据对象,后继的Stream对象主要是在ReadWrite方法上做了改进,。因此,了解TStream是掌握Stream对象管理的核心。Borland公司虽然提供了Stream对象的接口说明文档,但对于其实现和应用方法却没有提及,笔者是从Borland Delphi 2.0 Client/Server Suite 提供的源代码和部分例子程序中掌握了流式对象技术。

  下面就从TStream的属性和方法的实现开始。

  1. TStream属性的实现

  前面介绍过,TStream具有PositionSize两个属性,作为抽象数据类型,它抽象了在各种存储媒介中读写数据所需要经常访问的域。那么它们是怎样实现的呢?

  在自定义部件编写这一章中介绍过部件属性定义中的读写控制。PositionSize也作了读写控制。定义如下:

 

    property Position: Longint read GetPosition write SetPosition;

    property Size: Longint read GetSize;

 

  由上可知,Position是可读写属性,而Size是只读的。

  Position属性的实现就体现在GetPositionSetPosition。当在程序运行过程中,任何读取Position的值和给Position赋值的操作都会自动触发私有方法GetPositionSetPosition。两个方法的声明如下:

 

    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方法

  CopyFromStream对象中很有用的方法,它用于在不同存储媒介中拷贝数据。例如,内存与外部文件之间、内存与数据库字段之间等。它简化了许多内存分配、文件打开和读写等的细节,将所有拷贝操作都统一到Stream对象上。

  前面曾介绍:CopyFrom方法带SourceCount两个参数并返回长整型。该方法将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方法简单地调用虚拟函数ReadWrite来读写流中数据,它比ReadWrite增加了读写数据出错时的异常处理。

 

    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;

 

  ⑶ ReadComponentReadResHeaderReadComponentRes方法

  ReadComponent方法从当前流中读取部件。在实现上ReadComponent方法创建了一个TStream对象,并用TReaderReadRootComponent方法读部件。在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资源文件中的部件时调用,通常程序员不需自己调用。如果读取的不是资源文件ReadResHeader,将触发异常事件。

 

    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;

 

  ReadComponentResWindows资源文件中读取部件,为了判断是否是资源文件,它首先调用ReadResHeader方法,然后调用ReadComponent方法读取Instance指定的部件。下面是它的实现:

 

    function TStream.ReadComponentRes(Instance: TComponent): TComponent;

    begin

      ReadResHeader;

      Result := ReadComponent(Instance);

    end;

 

   与ReadComponentRes相应的写方法是WriteComponentResDelphi 调用这两个方法读写窗体文件(DFM文件),在后面书中会举用这两个方法读取DFM文件的例子。

  ⑷ WriteComponentWriteDescendant方法

  Stream对象的WriteDescendant方法在实现过程中,创建了TWriter对象,然后利用TWriterWriteDescendant方法将Instance写入流。而WriteComponent方法只是简单地调用WriteDescendant方法将Instance写入流。它们的实现如下:

  

    procedure TStream.WriteComponent(Instance: TComponent);

    begin

      WriteDescendent(Instance, nil);

    end;

 

    procedure TStream.WriteDescendent(Instance, Ancestor: TComponent);

    var

      Writer: TWriter;

    begin

      Writer := TWriter.Create(Self, 4096);

      try

        Writer.WriteDescendent(Instance, Ancestor);

      finally

        Writer.Free;

      end;

    end;

 

  ⑸ WriteDescendantResWriteComponentRes方法

  WriteDescendantRes方法用于将部件写入Windows资源文件;而WriteComponentRes 方法只是简单地调用WriteDescendantRes方法,它们的实现如下:

 

    procedure TStream.WriteComponentRes(const ResName: string; Instance:

                                                          TComponent);

    begin

      WriteDescendentRes(ResName, Instance, nil);

    end;

 

    procedure TStream.WriteDescendentRes(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;

         WriteDescendent(Instance, Ancestor);

         ImageSize := Position - Origin;

         Position := Origin - 4;

         WriteBuffer(ImageSize, SizeOf(Longint));

         Position := Origin + ImageSize;

       end;

 

  WriteCompnentRes是与ReadComponentRes相应的对象写方法,这两个方法相互配合可读取DelphiDFM文件,从而利用Delphi系统的功能。

 

1.2 THandleStream对象

 

  THandleStream对象的行为特别象FileStream对象,所不同的是它通过已创建的文件句柄而不是文件名来存储流中的数据。

  THandleStream对象定义了Handle属性,该属性提供了对文件句柄的只读访问,并且Handle属性可以作为DelphiRTL文件管理函数的参数,利用文件类函数来读写数据。THandleStream覆盖了构造函数Create,该函数带有Handle 参数,该参数指定与THandleStream对象相关的文件句柄。

 

1.2.1 THandleStream的属性的方法:

 

  1. Handle属性

  声明:property Handle: Integer;

     Handle属性提供了对文件句柄的只读访问,该句柄由THandleStream的构造方法Create传入。因此除了用THandleStream提供的方法外,也可以用文件管理函数对句柄进行操作。实际上,THandleStream的方法在实现上也是运用文件管理函数进行实际的读写操作。

  2. Create方法

  声明:constructor Create(AHandle: Integer);

  Create方法使用传入的Handle参数创建一个与特定文件句柄相联的THandleStream对象,并且将AHandle赋给流的Handle属性。

 

  3. ReadWriteSeek方法

  这三个方法是TStream的虚方法,只是在THandleStream 中覆盖了这三个方法,以实现特定媒介──文件的数据存取。后面会详细介绍这三个方法的实现。

 

1.2.2 THandleStream的实现原理

 

  THandleStream是从TStream继承来的,因此可以共用TStream中的属性和大多数方法。THandleStream在实现上主要是增加了一个属性Handle和覆盖了CreateReadWriteSeek四个方法。

  1. 属性的实现

  Handle属性的实现正如Delphi大多数属性的实现那样,先在对象定义的private部分声明一个存放数据的变量FHandle,然后在定义的public部分声明属性Handle,其中属性定义的读写控制部分加上只读控制,读控制只是直接读取FHandle变量的值,其实现如下:

 

    THandleStream = class(TStream)

    private

      FHandle: Integer;

    public

      

      property Handle: Integer read FHandle;

    end;

 

    2. 方法的实现

  THandleStreamCreate方法,以AHandle作为参数,在方法里面只是简单的将AHandle的值赋给FHandle,其实现如下:

 

    constructor THandleStream.Create(AHandle: Integer);

    begin

      FHandle := AHandle;

    end;

 

  为实现针对文件的数据对象存储,THandleStreamReadWriteSeek方法覆盖了TStream中的相应方法。它们的实现都调用了Windows的文件管理函数。

  Read方法调用FileRead函数实现文件读操作,其实现如下:

 

    function THandleStream.Read(var Buffer; Count: Longint): Longint;

    begin

      Result := FileRead(FHandle, Buffer, Count);

      if Result = -1 then Result := 0;

    end;

 

  Write方法调用FileWrite函数实现文件写操作,其实现如下:

 

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

    begin

      Result := FileWrite(FHandle, Buffer, Count);

      if Result = -1 then Result := 0;

    end;

 

  Seek方法调用FileSeek函数实现文件指针的移动,其实现如下:

 

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

    begin

      Result := FileSeek(FHandle, Offset, Origin);

    end;

 

1.3 TFileStream对象

 

  TFileStream对象是在磁盘文件上存储数据的Stream对象。TFileStream是从THandleStream继承下来的,它和THandleStream一样都是实现文件的存取操作。不同之处在于THandleStream用句柄访问文件,而TFileStream用文件名访问文件。实际上TFileStreamTHandleStream上的一层包装,其内核是THandleStream的属性和方法。

  TFileStream中没有增加新的属性和方法。它只是覆盖了的构造方法Create和析构方法Destory。在Create方法中带两个参数FileNameModeFileName描述要创建或打开的文件名,而Mode描述文件模式如fmCreatefmOpenReadfmOpenWrite等。Create方法首先使用FileCreateFileOpen函数创建或打开名为FileName的文件,再将得到的文件句柄赋给FHandleTFileStream的文件读写操作都是由从THandleStream继承的ReadWriteSeek方法来完成。下面是Create方法的实现。

  

    constructor TFileStream.Create(const FileName: string; Mode: Word);

    begin

      if Mode = fmCreate then

      begin

        FHandle := FileCreate(FileName);

        if FHandle < 0 then

          raise EFCreateError.CreateResFmt(SFCreateError, [FileName]);

      end else

      begin

        FHandle := FileOpen(FileName, Mode);

        if FHandle < 0 then

          raise EFOpenError.CreateResFmt(SFOpenError, [FileName]);

      end;

    end;

 

  如果Create方法中的文件创建或打开出错将触发一个异常事件。

  TFileStreamDestroy方法在FHande不小于零时,关闭文件。下面是Destroy的实现。

 

       destructor TFileStream.Destroy;

       begin

         if FHandle >= 0 then FileClose(FHandle);

       end;

 

 

20.1.4 TCustormMemoryStream对象

 

  TCustomMemoryStream是用于对内存媒介上的数据进行操作的对象,但它是抽象类,是用于内存流如TMemoryStreamTResourceStream的通用祖先对象。它定义了许多对于后继MemoryStream对象很重要的属性和方法。因此,虽然不直接使用TCustomMemoryStream对象,但也要详细介绍该对象,掌握它是掌握MemoryStream对象的基础。

  TCustomMemoryStream从抽象类TStream继承而来。它定义了Memory属性,该属性用来访问流的内部数据存储单元。它还覆盖了继承的ReadWrite方法,实现了对内存的读写访问操作,并且还提供了将内存中数据存入磁盘文件和复制到其它流的方法。下面介绍它的属性和方法。

 

1.4.1 TCustomMemoryStream的属性和方法

 

  1. Memory属性

  声明:property Memory: Pointer;

   通过 Memory属性可以对为内存流分配的内存缓冲池直接访问。Memory是个只读型的属性。如果要改变内存分配,可以调用SetPointer方法分配内存,并将指针分配给Memory属性。

  2. SetPointer方法

  声明:procedure SetPointer(Ptr: Pointer; Size: Longint);

    SetPointer方法将由Ptr传入的指针值赋给用于Memory属性的内部数据域,并将流的Size属性值设为由Size传入的值。

  在MemoryStream对象中调用SetPointer方法可以为流的存储分配或重分配内存。

  3. SaveToStream方法

  声明:procedure SaveToStream(Stream: TStream);

  SaveToStream方法将内存流中的完整数据写入Stream所指定的流。TCustomMemoryStream中没有定义相应的LoadFromStream方法,但在可实例化的TMemoryStream中定义了该方法。

  4. SaveToFile方法

  声明:procedure SaveToFile(const FileName: string);

  SaveToFile方法将内存流中的完整内容写入由FileName指定的文件。如果指定文件已经存在,则SaveToFile会覆盖它。这个方法使文件成为内存流内容的一份完整的拷贝。

  TCustomMemoryStream方法还覆盖了TStream中的ReadSeek方法。有关它们的介绍可以参考TStream对象的介绍,在实现原理部分还将介绍这两个方法。

 

20.1.4 .2 TCustomMemoryStream方法的实现原理

 

  TCustomMemoryStream方法继承了TStream对象,因此拥有了TStream的属性和大多数方法。它还增加了Memory属性,SetPointerSaveToStreamSaveToFile方法,并且覆盖了ReadSeek方法,以实现它在内存流上的特殊操作,下面介绍它们的实现。

  1. Memory属性和SetPointer方法

  如同Delphi自定义部件开发一章介绍的那样,Memory属性在实现上也在private部分定义了存放属性值的数据域FMemory。并且在属性声明中加上只读控制。Memory属性的读控制只是简单地读取FMemory的值。它的实现如下:

 

       TCustomMemoryStream = class(TStream)

       private

         FMemory: Pointer;

         FSize, FPosition: Longint;

         

       public

         

         property Memory: Pointer read FMemory;

       end;

 

  TStreamSizePosition属性的实现并没有在private部分声明一个数据域保存属性而是加上读写控制方法来提供属性值。在TCustomMemoryStream中定义了FSizeFPosition域,它们用于对象的内部使用以完成类似SizePosition的功能。在TCustomMemoryStream的许多方法中都用来了这两个域,因此先介绍一下。

  TCustomMemoryStreamSetPointer方法用到了FSize域。SePointer方法比较简单,它将传入的PtrSize值赋给FMemoryFSize,但并不执行内存的分配工作,实现如下:

 

       procedure TCustomMemoryStream.SetPointer(Ptr: Pointer; Size: Longint);

       begin

         FMemory := Ptr;

         FSize := Size;

       end;

 

  TCustomMemoryStream中的许多方法都是这种类型,貌似平淡无奇,但要记住TCustomMemoryStream是个抽象类,它的主要功能是搭框架,许多方法的具体实现体现在一些可实例化的对象如TMemoryStream中,这正是面向对象技术的精华所在,也是TCustomMemoryStream设计的妙处所在。

  2. ReadSeek方法

  前面讲了TCustomMemoryStream是个抽象类,不可实例化,它的许多域都没有初始化,如FMemory自始至终都只是没有指向实际内存区域的指针,内存池的分配都在后继的可实例化的对象中执行,如TMemory中,后面会介绍内存池在不同内存流中是如何分配的。

  TCustomMemoryStreamReadSeek方法都是只对FMemroyFSizeFPosition进行操作。

  Read方法调用了内存拷贝函数Move实现读操作,并且相应地移动指针位置,即设置FPosition的值。实现如下:

 

       function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint;

       begin

         if (FPosition >= 0) and (Count >= 0) then

         begin

           Result := FSize - FPosition;

           if Result > 0 then

           begin

             if Result > Count then Result := Count;

             Move(Pointer(Longint(FMemory) + FPosition)^, Buffer, Result);

             Inc(FPosition, Result);

             Exit;

           end;

         end;

         Result := 0;

       end;

 

  如果Count0或从当前指针到流的尾部只有零字节,则返回值为零。如果Count值大于当前指针到流的尾部的字节数即剩余字节数,则只读剩余字节数。因此在具体运用内存流的Read方法时要判断是否按需求读取数据了。

  Seek方法也是通过给PositionFSize数据域赋值来实现指针移动,其实现如下:

 

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

       begin

         case Origin of

           0: FPosition := Offset;

           1: Inc(FPosition, Offset);

           2: FPosition := FSize + Offset;

         end;

         Result := FPosition;

       end;

 

  Offse代表移动的偏移量。Origin代表移动的起点,值为0表示从文件头开始,值为1表示从当前位置开始,值为2表示从文件尾往前,这时OffSet一般为负数。Seek的实现没有越界的判断。

  3. SaveToStreamSaveToFile方法

  SaveToStream方法是将MemoryStream对象中的内容写入Stream所指定的流。其实现如下:

 

       procedure TCustomMemoryStream.SaveToStream(Stream: TStream);

       begin

         if FSize <> 0 then Stream.WriteBuffer(FMemory^, FSize);

       end;

 

  SaveToStream方法调用了StreamWriteBuffer方法,直接将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 SaveToFileLoadFromStreamLoadFromFile方法的实现都有类似的嵌套结构。

 

1.5 TMemoryStream对象

 

   TMemoryStream对象是一个管理动态内存中的数据的Stream对象,它是从TCustomMemoryStream中继承下来的,除了从TCustomMemoryStream中继承的属性和方法外,它还增加和覆盖了一些用于从磁盘文件和其它注台读数据的方法。它还提供了写入、消除内存内容的动态内存管理方法。下面介绍它的这些属性和方法。

 

1.5.1 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方法后,SizePosition属性都为0

  5. LoadFromStream方法

  声明:procedure LoadFromStream(Stream: TStream);

    LoadFromStream方法将Stream指定的流中的全部内容复制到MemoryStream中,复制过程将取代已有内容,使MemoryStream成为Stream的一份拷贝。

  6. LoadFromFile方法

  声明:procedure LoadFromFile(count FileName: String);

    LoadFromFile方法将FileName指定文件的所有内容复制到MemoryStream中,并取代已有内容。调用LoadFromFile方法后,MemoryStream将成为文件内容在内存中的完整拷贝。

 

1.5.2 TMemoryStream对象的实现原理

 

  TMemoryStreamTCustomMemoryStream对象直接继承,因此可以享用TCustomMemoryStream的属性和方法。前面讲过,TCustomMemoryStream是用于内存中数据操作的抽象对象,它为MemoryStream对象的实现提供了框架,框架中的内容还要由具体MemoryStream对象去填充。TMemoryStream对象就是按动态内存管理的需要填充框架中的具体内容。下面介绍TMemoryStream对象的实现。

  1. TMemoryStream属性的实现

  TMemoryStream在其protected部分增加了一个Capacity属性,该属性决定了MemoryStream所占动态内存的大小。TMemoryStream首先在private部分声明了FCapacity变量作为存储Capacity属性值的数据域,然后在protected部分声明了该属性。在属性声明的读控制部分简单读取FCapacity的值,在写控制处调用了方法SetCapacity。该方法除了给FCapacity赋值外还执行了修改Capacity属性所必需操作如状态改变等。

  下面是属性的实现:

 

       TMemoryStream = class(TCustomMemoryStream)

       private

         FCapacity: Longint;

         procedure SetCapacity(NewCapacity: Longint);

       protected

         

         property Capacity: Longint read FCapacity write SetCapacity;

       public

         

       end;

 

  写控制方法SetCapacity的实现是这样的:

 

       procedure TMemoryStream.SetCapacity(NewCapacity: Longint);

       begin

         SetPointer(Realloc(NewCapacity), FSize);

         FCapacity := NewCapacity;

       end;

 

  在SetCapacity 方法先是调用Realloc重新分配内存,然后用NewCapacity的值给FCapacity赋值。Realloc方法进行某些对象状态的改变。

  2. TMemoryStream对象方法的实现

  ⑴ Realloc方法

  Realloc方法是TMemoryStream动态内存分配的核心,它的SetSizeSetCapacity等方法最终都是调用Realloc进行内存的分配和初始化工作的。它的实现如下:

 

       const

         MemoryDelta = $2000;

 

       function TMemoryStream.Realloc(var NewCapacity: Longint): Pointer;

       begin

         if NewCapacity > 0 then

           NewCapacity := (NewCapacity + (MemoryDelta - 1)) and not (MemoryDelta - 1);

         Result := Memory;

         if NewCapacity <> FCapacity then

         begin

           if NewCapacity = 0 then

           begin

             GlobalFreePtr(Memory);

             Result := nil;

           end else

           begin

             if Capacity = 0 then

               Result := GlobalAllocPtr(HeapAllocFlags, NewCapacity)

             else

               Result := GlobalReallocPtr(Memory, NewCapacity, HeapAllocFlags);

             if Result = nil then raise EStreamError.CreateRes(SMemoryStreamError);

           end;

         end;

       end;

 

  Realloc方法是以8K为单位分配动态内存的,方法中的第一句if语句就是执行该操作。如果传入的NewCapacity参数值为0,则释放流中的内存。Realloc方法用GLobal FreePtr函数释放内存,用GlobalAllocPtr分配内存,用GlobalReallocPtr进行内存的重分配。如果原来的Capacity属性值为0,则调用Globa|AllocPtr否则调用GlobalReallocPtr。最后如果Resultnil则触发内存流错的异常事件,否则返回指向分配的内存的指针。

  ⑵ Write方法

  Write方法从内存流内部缓冲池的当前位置开始写入二进制数据。其实现如下:

 

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

       var

         Pos: Longint;

       begin

         if (FPosition >= 0) and (Count >= 0) then

         begin

           Pos := FPosition + Count;

           if Pos > 0 then

           begin

             if Pos > FSize then

             begin

               if Pos > FCapacity then

                 SetCapacity(Pos);

               FSize := Pos;

             end;

             System.Move(Buffer, Pointer(Longint(FMemory) + FPosition)^, Count);

             FPosition := Pos;

             Result := Count;

             Exit;

           end;

         end;

         Result := 0;

       end;

 

  Buffer中存储要写入流的二进制数据,如果要写入的数据的字节超出了流的内存池的大小,则调用SetCapacity方法再分配内存,然后用内存复制函数将Buffer中的数据复制到FMemory中。接着移动位置指针,并返回写入数据的字节数。分析这段程序可以知道,FCapacity的值和FSize的值是不同的。

  ⑶ Clear方法

  Clear方法消除内存流中的数据,将Memory属性置为nil,并将FSizeFPosition 的值设为0。其实现如下:

 

       procedure TMemoryStream.Clear;

       begin

         SetCapacity(0);

         FSize := 0;

         FPosition := 0;

       end;

 

  ⑷ LoadFromStreamLoadFromFile方法

  LoadFromStream方法首先根据传入的StreamSize属性值重新分配动态内存,然后调用StreamReadBuffer方法往FMemory中复制数据,结果Stream的全部内容在内存中有了一份完整拷贝。其实现如下:

 

       procedure TMemoryStream.LoadFromStream(Stream: TStream);

       var

         Count: Longint;

       begin

         Stream.Position := 0;

         Count := Stream.Size;

         SetSize(Count);

         if Count <> 0 then Stream.ReadBuffer(FMemory^, Count);

       end;

 

   LoadFromFileLoadFromStream是一对方法。LoadFromFile首先创建了一个TFileStream对象,然后调用LoadFromStream方法,将FileStream文件流中的数据写入MemoryStream中。

 

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段的。

 

1.7 TBlobStream对象

 

  从Delphi 数据库开发平台这个意义上说,TBlobStream 对象是个很重要的对象。TBlobStream对象提供了修改TBlobFieldTBytesFieldTVarBytesField中数据的技术。开发者可以象对待文件或流那样在数据库域中读写数据。

  传统数据库发展的一个重要趋向是往多媒体数据库发展。目前比较著名和流行的数据库都支持多媒体功能,多媒体数据存储中的一大难点是数据结构不规则,数据量大。各大数据库产品是采用BLOB技术解决多媒体数据存储中的问题。DelphiTBlobStream对象的意义就在于:一方面可以使Delphi应用程序充分利用多媒体数据库的数据管理能力;另一方面又能利用Object Pascal的强大程序设计能力给多媒体数据库提供全方向的功能扩展余地。

  使用TBlobStream对象可以在多媒体数据库的BLOB字段存储任意格式的数据。一般说来,许多多媒体数据库只能支持图像、语音或者OLE服务器支持的数据。利用TBlobStream则不同,只要是程序能够定义的数据格式,它都能在BLOB字段中读写,而不需要其它辅助工具。

  TBlobStream用构造方法Create建立数据库域和BLOB流的联接。用ReadWrite 方法访问和改变域中的内容;用Seek方法,在域中定位;用Truncate方法删除域中当前位置起所有的数据。

 

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

 

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 帮助文件。

 

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
振动传感器的种类丰富,按照工作原理的不同,能分为电涡流式振动传感器、电感式振动传感器、电容式振动传感器、压电式振动传感器和电阻应变式振动传感器等。以下是这几种振动传感器的工作原理和用途。 1、电涡流式振动传感器 电涡流式振动传感器是涡流效应为工作原理的振动式传感器,它属于非接触式传感器。电涡流式振动传感器是通过传感器的端部和被测对象之间距离上的变化,来测量物体振动参数的。电涡流式振动传感器主要用于振动位移的测量。 2、电感式振动传感器 电感式振动传感器是依据电磁感应原理设计的一种振动传感器。电感式振动传感器设置有磁铁和导磁体,对物体进行振动测量时,能将机械振动参数转化为电参量信号。电感式振动传感器能应用于振动速度、加速度等参数的测量。 3、电容式振动传感器 电容式振动传感器是通过间隙或公共面积的改变来获得可变电容,再对电容量进行测定而后得到机械振动参数的。电容式振动传感器可以分为可变间隙式和可变公共面积式两种,前者可以用来测量直线振动位移,后者可用于扭转振动的角位移测定。 4、压电式振动传感器 压电式振动传感器是利用晶体的压电效应来完成振动测量的,当被测物体的振动对压电式振动传感器形成压力后,晶体元件就会产生相应的电荷,电荷数即可换算为振动参数。压电式振动传感器还可以分为压电式加速度传感器、压电式力传感器和阻抗头。 5、电阻应变式振动传感器 电阻应变式振动传感器是以电阻变化量来表达被测物体机械振动量的一种振动传感器。电阻应变式振动传感器的实现方式很多,可以应用各种传感元件,其中较为常见的是电阻应变。
第 1章 概述 1 1.1 网络编程相关的基本概念 1 1.1.1 网络编程与进程通信 1 1.1.2 Internet中网间进程的标识 3 1.1.3 网络协议的特征 7 1.2 三类网络编程 10 1.2.1 基于TCP/IP协议栈的网络编程 10 1.2.2 基于WWW应用的网络编程 10 1.2.3 基于.NET框架的Web Services网络编程 10 1.3 客户机/服务器交互模式 13 1.3.1 网络应用软件的地位和功能 13 1.3.2 客户机/服务器模式 14 1.3.3 客户机与服务器的特性 15 1.3.4 容易混淆的术语 16 1.3.5 客户机与服务器的通信过程 16 1.3.6 网络协议与C/S模式的关系 17 1.3.7 错综复杂的C/S交互 17 1.3.8 服务器如何同时为多个客户机服务 18 1.3.9 标识一个特定服务 20 1.4 P2P模式 21 1.4.1 P2P技术的兴起 21 1.4.2 P2P的定义和特征 21 1.4.3 P2P的发展 22 1.4.4 P2P的关键技术 22 1.4.5 P2P系统的应用与前景 22 习题 23 第 2章 套接字网络编程基础 24 2.1 套接字网络编程接口的产生与发展 24 2.1.1 问题的提出 24 2.1.2 套接字编程接口起源于UNIX操作系统 25 2.1.3 套接字编程接口在Windows和Linux操作系统中得到继承和发展 25 2.1.4 套接字编程接口的两种实现方式 25 2.1.5 套接字通信与UNIX操作系统的输入/输出的关系 26 2.2 套接字编程的基本概念 27 2.2.1 什么是套接字 27 2.2.2 套接字的特点 28 2.2.3 套接字的应用场合 30 2.2.4 套接字使用的数据类型和相关的问题 30 2.3 面向连接的套接字编程 32 2.3.1 可靠的传输控制协议 32 2.3.2 套接字的工作过程 33 2.3.3 面向连接的套接字编程实例 34 2.3.4 进程的阻塞问题和对策 40 2.4 无连接的套接字编程 43 2.4.1 高效的用户数据报协议 43 2.4.2 无连接的套接字编程的两种模式 43 2.4.3 数据报套接字的对等模式编程实例 45 2.5 原始套接字 47 2.5.1 原始套接字的创建 47 2.5.2 原始套接字的使用 48 2.5.3 原始套接字应用实例 49 习题 51 第3章 WinSock编程 53 3.1 WinSock概述 53 3.2 WinSock库函数 55 3.2.1 WinSock的注册与注销 55 3.2.2 WinSock的错误处理函数 58 3.2.3 主要的WinSock函数 61 3.2.4 WinSock的辅助函数 74 3.2.5 WinSock的信息查询函数 77 3.2.6 WSAAsyncGetXByY类型的扩展函数 79 3.3 网络应用程序的运行环境 82 习题 84 第4章 MFC编程 85 4.1 MFC概述 85 4.1.1 MFC是一个编程框架 85 4.1.2 典型的MDI应用程序的构成 87 4.2 MFC和Win32 89 4.2.1 MFC对象和Windows对象的关系 89 4.2.2 几个主要的类 91 4.3 CObject类 95 4.3.1 CObject类的定义 95 4.3.2 CObject类的特性 96 4.4 消息映射的实现 98 4.5 MFC对象的创建 102 4.5.1 MFC对象的关系 102 4.5.2 MFC提供的接口 104 4.5.3 MFC对象的创建过程 104 4.6 应用程序的退出 107 习题 107 第5章 MFC WinSock类的 编程 109 5.1 CAsyncSocket类 110 5.1.1 使用CAsyncSocket类的一般步骤 110 5.1.2 创建CAsyncSocket类对象 111 5.1.3 关于CAsyncSocket类可以接受并处理的消息事件 112 5.1.4 客户端套接字对象请求连接到服务器端套接字对象 114 5.1.5 服务器接收客户机的连接请求 115 5.1.6 发送与接收流式数据 116 5.1.7 关闭套接字 118 5.1.8 错误处理 118 5.1.9 其他成员函数 119 5.2 CSocket类 120 5.2.1 创建CSocket对象 120 5.2.2 建立连接 120 5.2.3 发送和接收数据 120 5.2.4 CSocket类、CArchive类和CSocketFile类 121 5.2.5 关闭套接字和清除相关的对象 122 5.3 CSocket类的编程模型 122 5.4 用CAsyncSocket类实现聊天室程序 123 5.4.1 实现目标 123 5.4.2 创建客户端应用程序 124 5.4.3 客户端程序的类与消息驱动 134 5.4.4 客户端程序主要功能的代码和分析 135 5.4.5 创建服务器端程序 142 5.4.6 服务器端程序的流程和消息驱动 144 5.4.7 点对点交谈的服务器端程序主要功能的代码和分析 145 5.5 用CSocket类实现聊天室程序 151 5.5.1 聊天室程序的功能 151 5.5.2 创建聊天室的服务器端程序 151 5.5.3 聊天室服务器端程序的主要实现代码和分析 154 5.5.4 创建聊天室的客户端程序 162 5.5.5 聊天室客户端程序的主要实现代码和分析 163 习题 170 实验 170 第6章 WinInet编程 172 6.1 MFC WinInet类 172 6.1.1 概述 172 6.1.2 MFC WinInet所包含的类 173 6.1.3 使用WinInet类编程的一般步骤 174 6.1.4 创建CInternetSession类对象 175 6.1.5 查询或设置Internet请求选项 176 6.1.6 创建连接类对象 177 6.1.7 使用文件检索类 178 6.1.8 重载OnStatusCallback函数 179 6.1.9 创建并使用网络文件类对象 180 6.1.10 CInternteException类 183 6.2 用MFC WinInet类实现FTP客户端 183 6.2.1 程序要实现的功能 183 6.2.2 创建应用程序的过程 184 习题 186 实验 187 第7章 WinSock的多线程 编程 188 7.1 WinSock为什么需要多线程编程 188 7.1.1 WinSock的两种I/O模式 188 7.1.2 两种模式的优缺点及解决方法 189 7.2 Win32操作系统下的多进程多线程机制 189 7.2.1 Win32 OS是单用户多任务的操作系统 189 7.2.2 Win32 OS是支持多线程的操作系统 190 7.2.3 多线程机制在网络编程中的应用 191 7.3 VC++对多线程网络编程的支持 192 7.3.1 MFC支持的两种线程 192 7.3.2 创建MFC的工作线程 193 7.3.3 创建并启动用户界面线程 195 7.3.4 终止线程 198 7.4 多线程FTP客户端实例 200 7.4.1 编写线程函数 200 7.4.2 添加事件处理函数 206 习题 208 第8章 WinSock的I/O模型 209 8.1 select模型 210 8.2 WSAAsyncSelect异步I/O模型 212 8.3 WSAEventSelect事件选择模型 216 8.4 重叠I/O模型 221 8.4.1 重叠I/O模型的优点 221 8.4.2 重叠I/O模型的基本原理 221 8.4.3 重叠I/O模型的关键函数和数据结构 222 8.4.4 使用事件通知实现重叠模型的步骤 225 8.4.5 使用完成例程实现重叠模型的步骤 227 8.5 完成端口模型 229 8.5.1 什么是完成端口模型 229 8.5.2 使用完成端口模型的方法 230 习题 238 第9章 HTTP及编程 239 9.1 HTTP 239 9.1.1 HTTP的背景 239 9.1.2 HTTP的内容 240 9.1.3 HTTP消息的一般格式 242 9.1.4 HTTP请求的格式 243 9.1.5 HTTP响应的格式 245 9.1.6 访问认证 248 9.1.7 URL编码 249 9.1.8 HTTP的应用 250 9.2 利用CHtmlView类创建Web浏览器型的应用程序 250 9.2.1 CHtmlView类与WebBrowser控件 250 9.2.2 CHtmlView类的成员函数 251 9.2.3 创建一个Web浏览器型的应用程序的一般步骤 256 9.3 Web浏览器应用程序实例 261 9.3.1 程序实现的目标 261 9.3.2 创建实例程序 262 习题 265 实验 265 第 10章 电子邮件协议与编程 267 10.1 电子邮件系统的工作原理 267 10.1.1 电子邮件的特点 267 10.1.2 电子邮件系统的构成 267 10.1.3 电子邮件系统的实现 268 10.2 简单邮件传送协议 270 10.2.1 概述 270 10.2.2 SMTP客户机与SMTP服务器之间的会话 270 10.2.3 常用的SMTP命令 271 10.2.4 常用的SMTP响应码 273 10.2.5 SMTP的会话过程 274 10.2.6 使用WinSock来实现电子邮件客户机与服务器的会话 274 10.3 电子邮件信件结构详述 275 10.3.1 Internet文本信件的格式标准——RFC 822 275 10.3.2 信件的头部 276 10.3.3 构造和分析符合RFC 822标准的电子信件 281 10.4 MIME编码解码与发送附件 281 10.4.1 MIME概述 281 10.4.2 MIME定义的新的信头字段 282 10.4.3 MIME邮件的内容类型 283 10.4.4 MIME邮件的编码方式 292 10.5 POP3与接收电子邮件 294 10.5.1 POP3 294 10.5.2 POP3的会话过程 294 10.5.3 POP3会话的3个状态 295 10.5.4 POP3标准命令 296 10.5.5 接收电子邮件的一般步骤 298 10.6 接收电子邮件的程序实例 299 10.6.1 实例程序的目的和实现的技术要点 299 10.6.2 创建应用程序的过程 301 10.7 发送电子邮件的程序实例 302 10.7.1 实例程序的目的和实现的技术要点 302 10.7.2 创建应用程序的过程 303 习题 305 参考文献 307

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值