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);//計(jì)算資源大小,并寫入輔程尾部 Target.WriteBuffer(MyFileSize,sizeof(MyFileSize)); finally Target.Free; Source.Free; end; except Result:=False; Exit; end; Result:=True; end; 有了上面的基礎(chǔ),我們應(yīng)該很容易看得懂這個(gè)函數(shù)。其中參數(shù)SourceFile是要添加的文件,參數(shù)TargetFile是被添加到的目標(biāo)文件。比如說把a(bǔ).exe添加到b.exe里面可以:Cjt_AddtoFile('a.exe',b.exe');如果添加成功就返回True否則返回假。 根據(jù)上面的函數(shù)我們可以寫出相反的讀出函數(shù): 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; 其中參數(shù)SourceFile是已經(jīng)添加了文件的文件名稱,參數(shù)TargetFile是取出文件后保存的目標(biāo)文件名。比如說Cjt_LoadFromFile('b.exe','a.txt');在b.exe中取出文件保存為a.txt。如果取出成功就返回True否則返回假。 打開Delphi,新建一個(gè)工程,在窗口上放上一個(gè)Edit控件Edit1和兩個(gè)Button:Button1和Button2。Button的Caption屬性分別設(shè)置為“確定”和“取消”。在Button1的Click事件中寫代碼: var S:string; begin S:=ChangeFileExt(application.ExeName,'.Cjt'); if Edit1.Text='790617' then begin Cjt_LoadFromFile(Application.ExeName,S); {取出文件保存在當(dāng)前路徑下并命名"原文件.Cjt"} Winexec(pchar(S),SW_Show);{運(yùn)行"原文件.Cjt"} Application.Terminate;{退出程序} end else Application.MessageBox('密碼不對,請重新輸入!','密碼錯(cuò)誤',MB_ICONERROR+MB_OK); 編譯這個(gè)程序,并把EXE文件改名為head.exe。新建一個(gè)文本文件head.rc,內(nèi)容為: head exefile head.exe,然后把它們拷貝到Delphi的BIN目錄下,執(zhí)行Dos命令Brcc32.exe head.rc,將產(chǎn)生一個(gè)head.res的文件,這個(gè)文件就是我們要的資源文件,先留著。 我們的頭文件已經(jīng)建立了,下面我們來建立添加程序。 新建一個(gè)工程,放上以下控件:一個(gè)Edit,一個(gè)Opendialog,兩個(gè)Button1的Caption屬性分別設(shè)置為"選擇文件"和"加密"。在源程序中添加一句:{$R head.res}并把head.res文件拷貝到程序當(dāng)前目錄下。這樣一來就把剛才的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為自定義函數(shù),它的作用是把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; 注意:我們上面的函數(shù)只不過是簡單的把一個(gè)文件添加到另一個(gè)文件的尾部。實(shí)際應(yīng)用中可以改成可以添加多個(gè)文件,只要根據(jù)實(shí)際大小和個(gè)數(shù)定義好偏移地址就可以了。比如說文件捆綁機(jī)就是把兩個(gè)或者多個(gè)程序添加到一個(gè)頭文件里面。那些自解壓程序和安裝程序的原理也是一樣的,不過多了壓縮而已。比如說我們可以引用一個(gè)LAH單元,把流壓縮后再添加,這樣文件就會變的很小。讀出來時(shí)先解壓就可以了。另外,文中EXE加密器的例子還有很多不完善的地方,比如說密碼固定為"790617",取出EXE運(yùn)行后應(yīng)該等它運(yùn)行完畢后刪除等等,讀者可以自行修改。
procedure TForm1.Button1Click(Sender: TObject); begin if OpenPictureDialog1.Execute then Edit1.Text:=OpenPictureDialog1.FileName; end;
procedure TForm1.Button2Click(Sender: TObject); var HeadTemp:String; begin if Not FileExists(Edit1.Text) then begin Application.MessageBox('BMP圖片文件不存在,請重新選擇!','信息',MB_ICONINFORMATION+MB_OK) Exit; end; HeadTemp:=ChangeFileExt(Edit1.Text,'.exe'); if ExtractRes('exefile','head',HeadTemp) then if Cjt_AddtoFile(Edit1.Text,HeadTemp) then Application.MessageBox('EXE文件生成成功!','信息',MB_ICONINFORMATION+MB_OK) else begin if FileExists(HeadTemp) then DeleteFile(HeadTemp); Application.MessageBox('EXE文件生成失敗!','信息',MB_ICONINFORMATION+MB_OK) end; end; end. 怎么樣?很神奇吧:)把程序界面弄的漂亮點(diǎn),再添加一些功能,你會發(fā)現(xiàn)比起那些要注冊的軟件來也不會遜多少吧。 ----------------------------------------------------------------------- 實(shí)際應(yīng)用之三:利用流制作自己的OICQ
OICQ是深圳騰訊公司的一個(gè)網(wǎng)絡(luò)實(shí)時(shí)通訊軟件,在國內(nèi)擁有大量的用戶群。但OICQ必須連接上互聯(lián)網(wǎng)登陸到騰訊的服務(wù)器才能使用。所以我們可以自己寫一個(gè)在局部網(wǎng)里面使用。 OICQ使用的是UDP協(xié)議,這是一種無連接協(xié)議,即通信雙方不用建立連接就可以發(fā)送信息,所以效率比較高。Delphi本身自帶的FastNEt公司的NMUDP控件就是一個(gè)UDP協(xié)議的用戶數(shù)據(jù)報(bào)控件。不過要注意的是如果你使用了這個(gè)控件必須退出程序才能關(guān)閉計(jì)算機(jī),因?yàn)門NMXXX控件有BUG。所有nm控件的基礎(chǔ) PowerSocket用到的ThreadTimer,用到一個(gè)隱藏的窗口(類為TmrWindowClass)處理有硬傷。 出問題的地方: Psock::TThreadTimer::WndProc(var msg:TMessage) if msg.message=WM_TIMER then 他自己處理 msg.result:=0 else msg.result:=DefWindowProc(0,....) end 問題就出在調(diào)用 DefWindowProc時(shí),傳輸?shù)腍WND參數(shù)居然是常數(shù)0,這樣實(shí)際上DefWindowProc是不能工作的,對任何輸入的消息的調(diào)用均返回0,包括WM_QUERYENDsession,所以不能退出windows。由于DefWindowProc的不正常調(diào)用,實(shí)際上除WM_TIMER,其他消息由DefWindowProc處理都是無效的。 解決的辦法是在 PSock.pas 在 TThreadTimer.Wndproc 內(nèi) Result := DefWindowProc( 0, Msg, WPARAM, LPARAM ); 改為: Result := DefWindowProc( FWindowHandle, Msg, WPARAM, LPARAM ); 早期低版本的OICQ也有這個(gè)問題,如果不關(guān)閉OICQ的話,關(guān)閉計(jì)算機(jī)時(shí)屏幕閃了一下又返回了。 好了,廢話少說,讓我們編寫我們的OICQ吧,這個(gè)實(shí)際上是Delphi自帶的例子而已:) 新建一個(gè)工程,在FASTNET面版拖一個(gè)NMUDP控件到窗口,然后依次放上三個(gè)EDIT,名字分別為Editip、EditPort、EditMyTxt,三個(gè)按鈕BtSend、BtClear、BtSave,一個(gè)MEMOMemoReceive,一個(gè)SaveDialog和一個(gè)狀態(tài)條StatusBar1。當(dāng)用戶點(diǎn)擊BtSend時(shí),建立一個(gè)內(nèi)存流對象,把要發(fā)送的文字信息寫進(jìn)內(nèi)存流,然后NMUDP把流發(fā)送出去。當(dāng)NMUDP有數(shù)據(jù)接收時(shí),觸發(fā)它的DataReceived事件,我們在這里再把接收到的流轉(zhuǎn)換為字符信息,然后顯示出來。 注意:所有的流對象建立后使用完畢后要記得釋放(Free),其實(shí)它的釋構(gòu)函數(shù)應(yīng)該為Destroy,但如果建立流失敗的話,用Destroy會產(chǎn)生異常,而用Free的話程序會先檢查有沒有成功建立了流,如果建立了才釋放,所以用Free比較安全。 在這個(gè)程序中我們用到了NMUDP控件,它有幾個(gè)重要的屬性。RemoteHost表示遠(yuǎn)程電腦的IP或者計(jì)算機(jī)名,LocalPort是本地端口,主要監(jiān)聽有沒有數(shù)據(jù)傳入。而RemotePort是遠(yuǎn)程端口,發(fā)送數(shù)據(jù)時(shí)通過這個(gè)端口把數(shù)據(jù)發(fā)送出去。理解這些已經(jīng)可以看懂我們的程序了。
procedure TForm1.BtClearClick(Sender: TObject); begin MemoReceive.Clear; end;
procedure TForm1.BtSaveClick(Sender: TObject); begin if SaveDialog1.Execute then MemoReceive.Lines.SaveToFile(SaveDialog1.FileName); end;
procedure TForm1.EditMyTxtKeyPress(Sender: TObject; var Key: Char); begin if Key=#13 then BtSend.Click; end; end. 上面的程序跟OICQ相比當(dāng)然差之甚遠(yuǎn),因?yàn)镺ICQ利用的是Socket5通信方式。它上線時(shí)先從服務(wù)器取回好友信息和在線狀態(tài),發(fā)送超時(shí)還會將信息先保存在服務(wù)器,等對方下次上線后再發(fā)送然后把服務(wù)器的備份刪除。你可以根據(jù)前面學(xué)的概念來完善這個(gè)程序,比如說再添加一個(gè)NMUDP控件來管理在線狀態(tài),發(fā)送的信息先轉(zhuǎn)換成ASCII碼進(jìn)行與或運(yùn)行并加上一個(gè)頭信息,接收方接收信息后先判斷信息頭正確與否,如果正確才把信息解密顯示出來,這樣就提高了安全保密性。 另外,UDP協(xié)議還有一個(gè)很大的好處就是可以廣播,就是說處于一個(gè)網(wǎng)段的都可以接收到信息而不必指定具體的IP地址。網(wǎng)段一般分A、B、C三類, 1~126.XXX.XXX.XXX (A類網(wǎng)) :廣播地址為XXX.255.255.255 128~191.XXX.XXX.XXX(B類網(wǎng)):廣播地址為XXX.XXX.255.255 192~254.XXX.XXX.XXX(C類網(wǎng)):廣播地址為XXX.XXX.XXX.255 比如說三臺計(jì)算機(jī)192.168.0.1、192.168.0.10、192.168.0.18,發(fā)送信息時(shí)只要指定IP地址為192.168.0.255就可以實(shí)現(xiàn)廣播了。下面給出一個(gè)轉(zhuǎn)換IP為廣播IP的函數(shù),快拿去完善自己的OICQ吧^-^.
Function Trun_ip(S:string):string; var s1,s2,s3,ss,sss,Head:string; n,m:integer; begin sss:=S; n:=pos('.',s); s1:=copy(s,1,n); m:=length(s1); delete(s,1,m); Head:=copy(s1,1,(length(s1)-1)); n:=pos('.',s); s2:=copy(s,1,n); m:=length(s2); delete(s,1,m); n:=pos('.',s); s3:=copy(s,1,n); m:=length(s3); delete(s,1,m); ss:=sss; if strtoint(Head) in [1..126] then ss:=s1+'255.255.255'; //1~126.255.255.255 (A類網(wǎng)) if strtoint(Head) in [128..191] then ss:=s1+s2+'255.255';//128~191.XXX.255.255(B類網(wǎng)) if strtoint(Head) in [192..254] then ss:=s1+s2+s3+'255'; //192~254.XXX.XXX.255(C類網(wǎng)) Result:=ss; end;