Delphi 流

流的概念

流简单说是建立在面向对象基础上的一种抽象的处理数据的工具,它定义了一些处理数据的基本操作,如读取数据,写入数据等,程序员只需掌握对流进行操作,而不用关心流的另一头数据的真正流向。其实,流就是把整个对象转化为一个一个单字节数据,然后形成数据流,其形同把固体石块打碎成一颗颗细小的沙子一样,最后可以形成流沙。

流的主要功能

流的主要功能是对文件与非文件数据相互转换进行操作(即它们之间的I/O操作)。如将图片文件保存到数据库,那么数据库中的图片数据就变成非文件数据了,它只属于某记录某字段的数据。而诸如网络上传输的数据、内存中的存储的数据等也都属于非文件数据,这些数据要转变成文件也需要流操作。

Delphi 中流的基本概念及函数声明

㈠ 流的属性

在 Delphi 中,所有流对象的基类为 TStream 类,其中定义了所有流的共同属性和方法。以下为 TStream 类中定义的属性:

1.Size:此属性以字节返回流中数据大小。

2.Position:此属性控制流中存取指针的位置。

㈡ TStream定义的虚方法

1. Read: 此方法实现将数据从流中读出。

函数原形为:

Function Read(var Buffer;Count:Longint):Longint;virtual;abstract;

参数 Buffer 为数据读出时放置的缓冲区,Count 为需要读出的数据的字节数,该方法返回值为实际读出的字节数,它可以小于或等于Count中指定的值。

2. Write: 此方法实现将数据写入流中。

函数原形为:

Function Write(var Buffer;Count:Longint):Longint;virtual;abstract;

参数 Buffer 为将要写入流中的数据的缓冲区,Count 为数据的长度字节数,该方法返回值为实际写入流中的字节数。

3、Seek: 此方法实现流中读取指针的移动。

函数原形为:

Function Seek(Offset:Longint;Origint:Word):Longint;virtual;abstract;

参数 Offset 为偏移字节数,参数Origint指出 Offset 的实际意义,其可能的取值如下:

  • soFromBeginning:Offset为移动后指针距离数据开始的位置。此时Offset必须大于或者等于零。
  • soFromCurrent:Offset为移动后指针与当前指针的相对位置。
  • soFromEnd:Offset为移动后指针距离数据结束的位置。此时Offset必须小于或者等于零。

该方法返回值为移动后指针的位置。

4、Setsize: 此方法实现改变数据的大小。函数原形为:

Function Setsize(NewSize:Longint);virtual;

㈢ TStream类定义的静态方法

1. ReadBuffer: 此方法的作用是从流中当前位置读取数据。

函数原形为:

Procedure ReadBuffer(var Buffer;Count:Longint);

参数的定义跟上面的Read相同。注意:当读取的数据字节数与需要读取的字节数不相同时,将产生EReadError异常。

2. WriteBuffer: 此方法的作用是在当前位置向流写入数据。

函数原形为:

Procedure WriteBuffer(var Buffer;Count:Longint);

参数的定义跟上面的Write相同。注意:当写入的数据字节数与需要写入的字节数不相同时,将产生EWriteError异常。

3. CopyFrom: 此方法的作用是从其它流中拷贝数据流。

函数原形为:

Function CopyFrom(Source:Tstream;Count:Longint):Longint;

参数 Source 为提供数据的流,Count为拷贝的数据字节数:

  • 当Count大于0时,CopyFrom从Source参数的当前位置拷贝Count个字节的数据;
  • 当Count等于0时,CopyFrom设置Source参数的Position属性为0,然后拷贝Source的所有数据;

㈣ TStream派生类

1. TFileStream类(文件流)

使用 TFileStream 类来存取文件,首先要建立一个实例。声明如下:

constructor Create(const Filename:string;Mode:Word);

Filename为文件名(包括路径),参数Mode为打开文件的方式,它包括文件的打开模式共享模式,其可能的取值和意义如下:

打开模式:

  • fmCreate :用指定的文件名建立文件,如果文件已经存在则打开它。
  • fmOpenRead :以只读方式打开指定文件
  • fmOpenWrite :以只写方式打开指定文件
  • fmOpenReadWrite:以读写方式打开指定文件

共享模式:

  • fmShareCompat :共享模式与FCBs兼容
  • fmShareExclusive:不允许别的程序以任何方式打开该文件
  • fmShareDenyWrite:不允许别的程序以写方式打开该文件
  • fmShareDenyRead :不允许别的程序以读方式打开该文件
  • fmShareDenyNone :别的程序可以以任何方式打开该文件

2. TMemoryStream类(内存流)

实际应用内存流使用也非常多,就是说在内存中建立一个流对象,它的基本方法和函数跟上面是一样的。

Source:=TMemoryStream.Create;

3. TResourceStream(资源流)

资源流主要应用于对资源文件的操作。

㈤ 与流操作的一些相关函数和方法

1.sizeof(变量:Integer):获得变量所占空间的大小。

2.流类变量.SaveToFile(目标文件名):将流转变为文件保存。

3.其它类变量.SaveToStream(流类变量):将类变量内容转变为流。

4. 其它类变量.LoadFromFile(目标文件名):将文件加载到其它类变量。

5. 其它类变量.LoadFromStream(流类变量):将流类变量加载到其它类变量。

6. 流类变量.Free:释放流。

Delphi 流的常用操作方法

㈠ 将文件转换为文件流

var 
  Source: TFileStream;
begin   Source:=TFileStream.Create(源文件,fmOpenRead or fmShareExclusive); End;

以上代码的意义是:将源文件以只读或者禁止任何方式打开文件的方式打开,并创建成文件流的形式,最后将其赋值给文件流变量Source。

㈡ 将文件转换为内存流

var 
  Source: TMemoryStream;
begin   Source:=TMemoryStream.Create;   Source.LoadFromFile(源文件); End;

以上代码的意义是:创建一个内存流变量 Source,并将源文件加载到流变量Source中。

㈢ 将内存流保存成文件

var 
  Target: TMemoryStream;
begin   Target.SaveToFile('目标文件'); end;

注意,文件流一经创建就是以文件的形式保存在磁盘中,因此没有像内存流一样有转换成文件的操作。

㈣ 将其它类变量转变为流变量

var 
  BmS: TMemoryStream;   Bitmap1:TBitmap;
begin   Bitmap1.SaveToStream(BmS); end;

㈤ 将两个流合并

var
  Target, Source: TFileStream;
begin
  Source:=TFileStream.Create('辅文件', fmOpenRead or fmShareExclusive);
  Target:=TFileStream.Create('主文件', fmOpenWrite or fmShareExclusive);
  Target.Seek(0, soFromEnd);//将指针移到主文件的末尾
  Target.CopyFrom(Source, 0);//注意:两个流合并使用CopyFrom方法。
  Target.Free;
  Source.Free;
end;

以上程序实际为两个文件合并的操作,即将辅文件添加到主文件的后面,最后变成一个文件。需要注意的是两个流合并需要先将指针移到主文件末尾,然后再将辅文件添加进来。该方法可用于文件加密、木马捆绑、病毒传染等。

㈥ 将两个流分离

var
  Source: TFileStream;
  Target: TMemoryStream;
  MyFileSize: integer;
begin
  MyFileSize:=10//辅文件长度;
  Target :=TMemoryStream.Create;
  Source:=TFileStream.Create('主文件', fmOpenRead  or fmShareDenyNone);
  Source.Seek(-MyFileSize, soFromEnd); //定位到资源位置 
  //注意:这里面-sizeof(MyFileSize)的原表达式是0-SizeOf(MyFileSize)。意思将指针移到离末尾MyFileSize个字节处,准备从该处起读取数据。参数soFromEnd就是“离末尾”的意思,如果参数是soFromBeginning,那就是“离前面”的意思。
  Target.CopyFrom(Source, MyFileSize); //取出资源
  Target.SaveToFile(TargetFile); //存放到文件
  Target.Free;
  Source.Free;
end;

上述程序实现的功能实际是将两个合并的文件分离开来。该方法还可用于文件的分割、文件加密等。学习时请注意其指针移动的方法。


应用:利用流制作EXE文件加密器、捆绑、自解压文件及安装程序

EXE文件加密器的原理:建立两个文件,一个用来添加资源到另外一个EXE文件里面,称为添加程序。另外一个被添加的EXE文件称为头文件。该程序的功能是把添加到自己里面的文件读出来。Windows下的EXE文件结构比较复杂,有的程序还有校验和,当发现自己被改变后会认为自己被病毒感染而拒绝执行。所以我们把文件添加到自己的程序里面,这样就不会改变原来的文件结构了。我们先写一个添加函数,该函数的功能是把一个文件当作一个流添加到另外一个文件的尾部。函数如下:

Function Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean; 
var 
  Target,Source:TFileStream; 
  MyFileSize:integer; 
begin 
  try 
    Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareExclusive); 
    Target:=TFileStream.Create(TargetFile,fmOpenWrite or fmShareExclusive); 
    try 
      Target.Seek(0,soFromEnd);//往尾部添加资源 
      Target.CopyFrom(Source,0); 
      MyFileSize:=Source.Size + Sizeof(MyFileSize);//计算资源大小,并写入辅程尾部 
      Target.WriteBuffer(MyFileSize,sizeof(MyFileSize)); 
    finally 
      Target.Free; 
      Source.Free; 
    end; 
  except 
    Result:=False; 
    Exit; 
  end; 
  Result:=True; 
end; 

其中参数SourceFile是要添加的文件,参数TargetFile是被添加到的目标文件。比如说把a.exe添加到b.exe里面可以:

Cjt_AddtoFile('a.exe',b.exe');

如果添加成功就返回True,否则返回假。 

根据上面的函数我们可以写出相反的读出函数: 

Function Cjt_LoadFromFile(SourceFile,TargetFile :string):Boolean; 
var 
  Source:TFileStream; 
  Target:TMemoryStream; 
  MyFileSize:integer; 
begin 
  try 
    Target:=TMemoryStream.Create; 
    Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareDenyNone); 
    try 
      Source.Seek(-sizeof(MyFileSize),soFromEnd); 
      Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//读出资源大小 
      Source.Seek(-MyFileSize,soFromEnd);//定位到资源位置 
      Target.CopyFrom(Source,MyFileSize-sizeof(MyFileSize));//取出资源 
      Target.SaveToFile(TargetFile);//存放到文件 
    finally 
      Target.Free; 
      Source.Free; 
    end; 
  except 
    Result:=false; 
    Exit; 
  end; 
  Result:=true; 
end; 

其中参数SourceFile是已经添加了文件的文件名称,参数TargetFile是取出文件后保存的目标文件名。比如:

Cjt_LoadFromFile('b.exe','a.txt');

在b.exe中取出文件保存为a.txt。如果取出成功就返回True否则返回假。 

打开Delphi,新建一个工程,在窗口上放上一个Edit控件Edit1和两个Button:Button1和Button2。Button的Caption属性分别设置为“确定”和“取消”。

在Button1的Click事件中写代码: 

var S:string; 
begin 
  S:=ChangeFileExt(Application.ExeName,'.Cjt'); 
  if Edit1.Text='790617' then 
  begin 
    Cjt_LoadFromFile(Application.ExeName,S); 
    {取出文件保存在当前路径下并命名"原文件.Cjt"} 
    Winexec(pchar(S),SW_Show);{运行"原文件.Cjt"} 
    Application.Terminate;{退出程序} 
  end 
  else 
    Application.MessageBox('密码不对,请重新输入!','密码错误',MB_ICONERROR+MB_OK); 
end;

编译这个程序,并把EXE文件改名为head.exe。新建一个文本文件head.rc,内容为: head exefile head.exe,然后把它们拷贝到Delphi的BIN目录下,执行Dos命令Brcc32.exe head.rc,将产生一个head.res的文件,这个文件就是我们要的资源文件。 

我们的头文件已经建立了,下面我们来建立添加程序。 
新建一个工程,放上以下控件:一个Edit,一个Opendialog,两个Button1的Caption属性分别设置为"选择文件"和"加密"。在源程序中添加一句:{$R head.res}并把head.res文件拷贝到程序当前目录下。这样一来就把刚才的head.exe跟程序一起编译了。 

在Button1的Cilck事件里面写下代码: 

if OpenDialog1.Execute then Edit1.Text:=OpenDialog1.FileName; 

在Button2的Cilck事件里面写下代码: 

var S:String; 
begin 
  S:=ExtractFilePath(Edit1.Text); 
  if ExtractRes('exefile','head',S+'head.exe') then 
    if Cjt_AddtoFile(Edit1.Text,S+'head.exe') then 
      if DeleteFile(Edit1.Text) then 
        if RenameFile(S+'head.exe',Edit1.Text) then 
        Application.MessageBox('文件加密成功!','信息',MB_ICONINFORMATION+MB_OK) 
      else 
       begin 
            if FileExists(S+'head.exe') then DeleteFile(S+'head.exe'); 
          Application.MessageBox('文件加密失败!','信息',MB_ICONINFORMATION+MB_OK) 
       end; 
end; 

其中ExtractRes为自定义函数,它的作用是把head.exe从资源文件中取出来。 

Function ExtractRes(ResType, ResName, ResNewName : String):boolean; 
var 
  Res : TResourceStream; 
begin 
  try 
    Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType)); 
    try 
      Res.SavetoFile(ResNewName); 
      Result:=true; 
    finally 
      Res.Free; 
    end; 
  except 
    Result:=false; 
  end; 
end; 

注意:我们上面的函数只不过是简单的把一个文件添加到另一个文件的尾部。实际应用中可以改成可以添加多个文件,只要根据实际大小和个数定义好偏移地址就可以了。比如说文件捆绑机就是把两个或者多个程序添加到一个头文件里面。那些自解压程序和安装程序的原理也是一样的,不过多了压缩而已。比如说我们可以引用一个LAH单元,把流压缩后再添加,这样文件就会变的很小。读出来时先解压就可以了。另外,文中EXE加密器的例子还有很多不完善的地方,比如说密码固定为"790617",取出EXE运行后应该等它运行完毕后删除等等,读者可以自行修改。

原文地址:https://www.cnblogs.com/ivantang/p/3849583.html