什麼是流?流,簡單來說就是建立在物件導向基礎上的一種抽象的處理資料的工具。在流中,定義了一些處理資料的基本操作,如讀取資料,寫入資料等,程式員是對流進行所有操作的,而不用關心流的另一頭資料的真正流向。流不但可以處理檔案,還可以處理動態記憶體、網路資料等多種資料形式。如果你對流的操作非常熟練,在程式中利用流的方便性,寫起程式會大大提高效率的。
下面,筆者通過四個執行個體:EXE檔案加密器、電子賀卡、自製OICQ和網路螢幕傳輸來說明Delphi編程中“流”的利用。這些例子中的一些技巧曾經是很多軟體的秘密而不公開的,現在大家可以無償的直接引用其中的代碼了。
“萬丈高樓平地起”,在分析執行個體之前,我們先來瞭解一下流的基本概念和函數,只有在理解了這些基本的東西後我們才能進行下一步。請務必認真領會這些基本方法。當然,如果你對它們已經很熟悉了,則可以跳過這一步。
一、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還有其它衍生類別,其中最常用的是TFileStream類。使用TFileStream類來存取檔案,首先要建立一個執行個體。聲明如下:
constructor Create(const Filename:string;Mode:Word);
Filename為檔案名稱(包括路徑),參數Mode為開啟檔案的方式,它包括檔案的開啟模式和共用模式,其可能的取值和意義如下:
開啟模式:
fmCreate :用指定的檔案名稱建立檔案,如果檔案已經存在則開啟它。
fmOpenRead :以唯讀方式開啟指定檔案
fmOpenWrite :以唯寫方式開啟指定檔案
fmOpenReadWrite:以寫寫方式開啟指定檔案
共用模式:
fmShareCompat :共用模式與FCBs相容
fmShareExclusive:不允許別的程式以任何方式開啟該檔案
fmShareDenyWrite:不允許別的程式以寫方式開啟該檔案
fmShareDenyRead :不允許別的程式以讀方式開啟該檔案
fmShareDenyNone :別的程式可以以任何方式開啟該檔案
TStream還有一個衍生類別TMemoryStream,實際應用中用的次數也非常頻繁。它叫記憶體流,就是說在記憶體中建立一個流對象。它的基本方法和函數跟上面是一樣的。
好了,有了上面的基礎後,我們就可以開始我們的編程之行了。
-----------------------------------------------------------------------
二、實際應用之一:利用流製作EXE檔案加密器、捆綁、自解壓檔案及安裝程式
我們先來說一下如何製作一個EXE檔案加密器吧。
EXE檔案加密器的原理:建立兩個檔案,一個用來添加資源到另外一個EXE檔案裡面,稱為添加程式。另外一個被添加的EXE檔案稱為標頭檔。該程式的功能是把添加到自己裡面的檔案讀出來。Windows下的EXE檔案結構比較複雜,有的程式還有校正和,當發現自己被改變後會認為自己被病毒感染而拒絕執行。所以我們把檔案添加到自己的程式裡面,這樣就不會改變原來的檔案結構了。我們先寫一個添加函數,該函數的功能是把一個檔案當作一個流添加到另外一個檔案的尾部。函數如下:
Function Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;<br /> var<br /> Target,Source:TFileStream;<br /> MyFileSize:integer;<br /> begin<br /> try<br /> Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareExclusive);<br /> Target:=TFileStream.Create(TargetFile,fmOpenWrite or fmShareExclusive);<br /> try<br /> Target.Seek(0,soFromEnd);//往尾部添加資源<br /> Target.CopyFrom(Source,0);<br /> MyFileSize:=Source.Size+Sizeof(MyFileSize);//計算資源大小,並寫入輔程尾部<br /> Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));<br /> finally<br /> Target.Free;<br /> Source.Free;<br /> end;<br /> except<br /> Result:=False;<br /> Exit;<br /> end;<br /> Result:=True;<br /> end;<br />
有了上面的基礎,我們應該很容易看得懂這個函數。其中參數SourceFile是要添加的檔案,參數TargetFile是被添加到的目標檔案。比如說把a.exe添加到b.exe裡面可以:Cjt_AddtoFile('a.exe',b.exe');如果添加成功就返回True否則返回假。
根據上面的函數我們可以寫出相反的讀出函數:
Function Cjt_LoadFromFile(SourceFile,TargetFile :string):Boolean;<br /> var<br /> Source:TFileStream;<br /> Target:TMemoryStream;<br /> MyFileSize:integer;<br /> begin<br /> try<br /> Target:=TMemoryStream.Create;<br /> Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareDenyNone);<br /> try<br /> Source.Seek(-sizeof(MyFileSize),soFromEnd);<br /> Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//讀出資源大小<br /> Source.Seek(-MyFileSize,soFromEnd);//定位到資源位置<br /> Target.CopyFrom(Source,MyFileSize-sizeof(MyFileSize));//取出資源<br /> Target.SaveToFile(TargetFile);//存放到檔案<br /> finally<br /> Target.Free;<br /> Source.Free;<br /> end;<br /> except<br /> Result:=false;<br /> Exit;<br /> end;<br /> Result:=true;<br /> end;<br />
其中參數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;<br /> begin<br /> S:=ChangeFileExt(Application.ExeName,'.Cjt');<br /> if Edit1.Text='790617' then<br /> begin<br /> Cjt_LoadFromFile(Application.ExeName,S);<br /> {取出檔案儲存在當前路徑下並命名"原檔案.Cjt"}<br /> Winexec(pchar(S),SW_Show);{運行"原檔案.Cjt"}<br /> Application.Terminate;{退出程式}<br /> end<br /> else<br /> Application.MessageBox('密碼不對,請重新輸入!','密碼錯誤',MB_ICONERROR+MB_OK);
編譯這個程式,並把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;<br /> 在Button2的Cilck事件裡面寫下代碼:<br /> var S:String;<br /> begin<br /> S:=ExtractFilePath(Edit1.Text);<br /> if ExtractRes('exefile','head',S+'head.exe') then<br /> if Cjt_AddtoFile(Edit1.Text,S+'head.exe') then<br /> if DeleteFile(Edit1.Text) then<br /> if RenameFile(S+'head.exe',Edit1.Text) then<br /> Application.MessageBox('檔案加密成功!','資訊',MB_ICONINFORMATION+MB_OK)<br /> else<br /> begin<br /> if FileExists(S+'head.exe') then DeleteFile(S+'head.exe');<br /> Application.MessageBox('檔案加密失敗!','資訊',MB_ICONINFORMATION+MB_OK)<br /> end;<br /> end;<br />
其中ExtractRes為自訂函數,它的作用是把head.exe從資源檔中取出來。
Function ExtractRes(ResType, ResName, ResNewName : String):boolean;<br /> var<br /> Res : TResour
下面這個例子更貼近你的想法:
三、實際應用之二:利用流製作可執行電子賀卡
我們經常看到一些電子賀卡之類的製作軟體,可以讓你自己選擇圖片,然後它會產生一個EXE可執行檔給你。開啟賀卡時就會一邊放音樂一邊顯示出圖片來。現在學了流操作之後,我們也可以做一個了。
添加圖片過程我們可以直接用前面的Cjt_AddtoFile,而現在要做的是如何把映像讀出並顯示。我們用前面的Cjt_LoadFromFile先把圖片讀出來儲存為檔案再調入也是可以的,但是還有更簡單的方法,就是直接把檔案流讀出來顯示,有了流這個利器,一切都變的簡單了。
現在的圖片比較流行的是BMP格式和JPG格式。我們現在就針對這兩種圖片寫出讀取並顯示函數。
Function Cjt_BmpLoad(ImgBmp:TImage;SourceFile:String):Boolean;<br /> var<br /> Source:TFileStream;<br /> MyFileSize:integer;<br /> begin<br /> Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareDenyNone);<br /> try<br /> try<br /> Source.Seek(-sizeof(MyFileSize),soFromEnd);<br /> Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//讀出資源<br /> Source.Seek(-MyFileSize,soFromEnd);//定位到資源開始位置<br /> ImgBmp.Picture.Bitmap.LoadFromStream(Source);<br /> finally<br /> Source.Free;<br /> end;<br /> except<br /> Result:=False;<br /> Exit;<br /> end;<br /> Result:=True;<br /> end;<br />
上面是讀出BMP圖片的,下面的是讀出JPG圖片的函數,因為要用到JPG單元,所以要在程式中添加一句:uses jpeg。
Function Cjt_JpgLoad(JpgImg:Timage;SourceFile:String):Boolean;<br /> var<br /> Source:TFileStream;<br /> MyFileSize:integer;<br /> Myjpg: TJpegImage;<br /> begin<br /> try<br /> Myjpg:= TJpegImage.Create;<br /> Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareDenyNone);<br /> try<br /> Source.Seek(-sizeof(MyFileSize),soFromEnd);<br /> Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));<br /> Source.Seek(-MyFileSize,soFromEnd);<br /> Myjpg.LoadFromStream(Source);<br /> JpgImg.Picture.Bitmap.Assign(Myjpg);<br /> finally<br /> Source.Free;<br /> Myjpg.free;<br /> end;<br /> except<br /> Result:=false;<br /> Exit;<br /> end;<br /> Result:=true;<br /> end;<br />
有了這兩個函數,我們就可以製作讀出程式了。下面我們以BMP圖片為例:
運行Delphi,建立一個工程,放上一個顯示影像控制Image1。在視窗的Create事件中寫上一句就可以了:
Cjt_BmpLoad(Image1,Application.ExeName);
這個就是標頭檔了,然後我們用前面的方法產生一個head.res資源檔。
下面就可以開始製作我們的添加程式了。全部代碼如下:
unit Unit1; </p><p> interface </p><p> uses<br /> Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,<br /> ExtCtrls, StdCtrls, ExtDlgs; </p><p> type<br /> TForm1 = class(TForm)<br /> Edit1: TEdit;<br /> Button1: TButton;<br /> Button2: TButton;<br /> OpenPictureDialog1: TOpenPictureDialog;<br /> procedure FormCreate(Sender: TObject);<br /> procedure Button1Click(Sender: TObject);<br /> procedure Button2Click(Sender: TObject);<br /> private<br /> Function ExtractRes(ResType, ResName, ResNewName : String):boolean;<br /> Function Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;<br /> { Private declarations }<br /> public<br /> { Public declarations }<br /> end; </p><p> var<br /> Form1: TForm1; </p><p> implementation </p><p> {$R *.DFM}<br /> Function TForm1.ExtractRes(ResType, ResName, ResNewName : String):boolean;<br /> var<br /> Res : TResourceStream;<br /> begin<br /> try<br /> Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));<br /> try<br /> Res.SavetoFile(ResNewName);<br /> Result:=true;<br /> finally<br /> Res.Free;<br /> end;<br /> except<br /> Result:=false;<br /> end;<br /> end;<br /> Function TForm1.Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;<br /> var<br /> Target,Source:TFileStream;<br /> MyFileSize:integer;<br /> begin<br /> try<br /> Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareExclusive);<br /> Target:=TFileStream.Create(TargetFile,fmOpenWrite or fmShareExclusive);<br /> try<br /> Target.Seek(0,soFromEnd);//往尾部添加資源<br /> Target.CopyFrom(Source,0);<br /> MyFileSize:=Source.Size+Sizeof(MyFileSize);//計算資源大小,並寫入輔程尾部<br /> Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));<br /> finally<br /> Target.Free;<br /> Source.Free;<br /> end;<br /> except<br /> Result:=False;<br /> Exit;<br /> end;<br /> Result:=True;<br /> end;<br /> procedure TForm1.FormCreate(Sender: TObject);<br /> begin<br /> Caption:='Bmp2Exe示範程式.作者:陳經韜';<br /> Edit1.Text:='';<br /> OpenPictureDialog1.DefaultExt := GraphicExtension(TBitmap);<br /> OpenPictureDialog1.Filter := GraphicFilter(TBitmap); </p><p> Button1.Caption:='選擇BMP圖片';<br /> Button2.Caption:='產生EXE';<br /> end; </p><p> procedure TForm1.Button1Click(Sender: TObject);<br /> begin<br /> if OpenPictureDialog1.Execute then<br /> Edit1.Text:=OpenPictureDialog1.FileName;<br /> end; </p><p> procedure TForm1.Button2Click(Sender: TObject);<br /> var<br /> HeadTemp:String;<br /> begin<br /> if Not FileExists(Edit1.Text) then<br /> begin<br /> Application.MessageBox('BMP圖片檔案不存在,請重新選擇!','資訊',MB_ICONINFORMATION+MB_OK)<br /> Exit;<br /> end;<br /> HeadTemp:=ChangeFileExt(Edit1.Text,'.exe');<br /> if ExtractRes('exefile','head',HeadTemp) then<br /> if Cjt_AddtoFile(Edit1.Text,HeadTemp) then<br /> Application.MessageBox('EXE檔案產生成功!','資訊',MB_ICONINFORMATION+MB_OK)<br /> else<br /> begin<br /> if FileExists(HeadTemp) then DeleteFile(HeadTemp);<br /> Application.MessageBox('EXE檔案產生失敗!','資訊',MB_ICONINFORMATION+MB_OK)<br /> end;<br /> end;<br /> end.<br />
怎麼樣?很神奇吧:)把程式介面弄的漂亮點,再添加一些功能,你會發現比起那些要註冊的軟體來也不會遜多少吧。