พูดถึงการประยุกต์ใช้ "flow" ในการเขียนโปรแกรม Delphi
เฉินจิงเทา
กระแสคืออะไร? พูดง่ายๆ ก็คือ Stream คือเครื่องมือประมวลผลข้อมูลเชิงนามธรรมที่อิงตามเชิงวัตถุ ในสตรีม มีการกำหนดการดำเนินการพื้นฐานบางอย่างสำหรับการประมวลผลข้อมูล เช่น การอ่านข้อมูล การเขียนข้อมูล ฯลฯ โปรแกรมเมอร์จะดำเนินการทั้งหมดบนสตรีมโดยไม่สนใจทิศทางการไหลที่แท้จริงของข้อมูลที่ปลายอีกด้านของสตรีม สตรีมไม่เพียงแต่สามารถประมวลผลไฟล์เท่านั้น แต่ยังรวมถึงหน่วยความจำแบบไดนามิก ข้อมูลเครือข่าย และรูปแบบข้อมูลอื่นๆ หากคุณมีความเชี่ยวชาญในการดำเนินการสตรีมและใช้ความสะดวกของสตรีมในโปรแกรมของคุณ ประสิทธิภาพของการเขียนโปรแกรมจะดีขึ้นอย่างมาก
ด้านล่าง ผู้เขียนใช้ตัวอย่างสี่ตัวอย่าง: ตัวเข้ารหัสไฟล์ EXE บัตรอวยพรอิเล็กทรอนิกส์ OICQ ที่สร้างขึ้นเอง และการส่งผ่านหน้าจอเครือข่ายเพื่อแสดงการใช้ "สตรีม" ในการเขียนโปรแกรม Delphi เทคนิคบางอย่างในตัวอย่างเหล่านี้เคยเป็นความลับของซอฟต์แวร์หลายตัวและไม่เปิดให้สาธารณะชน ในปัจจุบัน ทุกคนสามารถเสนอราคาโค้ดได้โดยตรงฟรี
"อาคารสูงลอยขึ้นจากพื้นดิน" ก่อนที่จะวิเคราะห์ตัวอย่าง เรามาทำความเข้าใจแนวคิดพื้นฐานและหน้าที่ของการไหลกันก่อน หลังจากเข้าใจสิ่งพื้นฐานเหล่านี้แล้วเท่านั้นที่เราจะไปยังขั้นตอนต่อไปได้ โปรดแน่ใจว่าได้เข้าใจวิธีการพื้นฐานเหล่านี้อย่างถี่ถ้วน แน่นอน หากคุณคุ้นเคยกับสิ่งเหล่านี้แล้ว คุณสามารถข้ามขั้นตอนนี้ได้
1. แนวคิดพื้นฐานและการประกาศฟังก์ชันของสตรีมใน Delphi
ใน Delphi คลาสพื้นฐานของสตรีมออบเจ็กต์ทั้งหมดคือคลาส TStream ซึ่งกำหนดคุณสมบัติและวิธีการทั่วไปของสตรีมทั้งหมด
คุณสมบัติที่กำหนดไว้ในคลาส TStream มีการแนะนำดังต่อไปนี้:
1. ขนาด: คุณสมบัตินี้ส่งคืนขนาดของข้อมูลในสตรีมเป็นไบต์
2. ตำแหน่ง: คุณลักษณะนี้ควบคุมตำแหน่งของตัวชี้การเข้าถึงในโฟลว์
มีสี่วิธีเสมือนที่กำหนดไว้ใน Tstream:
1. อ่าน: วิธีการนี้จะอ่านข้อมูลจากสตรีม ต้นแบบฟังก์ชันคือ:
ฟังก์ชั่นอ่าน (var Buffer;Count:Longint):Longint;virtual;abstract;
บัฟเฟอร์พารามิเตอร์คือบัฟเฟอร์ที่วางไว้เมื่ออ่านข้อมูล Count คือจำนวนไบต์ของข้อมูลที่จะอ่าน ค่าที่ส่งคืนของวิธีนี้คือจำนวนไบต์ที่อ่านจริงซึ่งอาจน้อยกว่าหรือเท่ากับค่าที่ระบุ นับ.
2. เขียน: วิธีการนี้จะเขียนข้อมูลลงในสตรีม ต้นแบบฟังก์ชันคือ:
ฟังก์ชั่นเขียน (var Buffer;Count:Longint):Longint;virtual;abstract;
พารามิเตอร์ Buffer คือบัฟเฟอร์ของข้อมูลที่จะเขียนลงในสตรีม Count คือความยาวของข้อมูลเป็นไบต์ และค่าที่ส่งคืนของวิธีนี้คือจำนวนไบต์ที่เขียนลงในสตรีมจริงๆ
3. ค้นหา: วิธีการนี้ใช้การเคลื่อนไหวของตัวชี้การอ่านในสตรีม ต้นแบบฟังก์ชันคือ:
ฟังก์ชั่นค้นหา (ออฟเซ็ต: Longint; ต้นกำเนิด: Word): Longint; เสมือน; นามธรรม;
พารามิเตอร์ Offset คือจำนวนไบต์ออฟเซ็ต และพารามิเตอร์ Origin ชี้ให้เห็นความหมายที่แท้จริงของ Offset ค่าที่เป็นไปได้มีดังนี้:
soFromBeginning:Offset คือตำแหน่งของตัวชี้จากจุดเริ่มต้นของข้อมูลหลังการเคลื่อนไหว ในขณะนี้ออฟเซ็ตจะต้องมากกว่าหรือเท่ากับศูนย์
soFromCurrent:Offset คือตำแหน่งสัมพัทธ์ของตัวชี้หลังการเคลื่อนไหวและตัวชี้ปัจจุบัน
soFromEnd:Offset คือตำแหน่งของตัวชี้จากจุดสิ้นสุดของข้อมูลหลังการเคลื่อนไหว ในขณะนี้ออฟเซ็ตจะต้องน้อยกว่าหรือเท่ากับศูนย์ ค่าที่ส่งคืนของวิธีนี้คือตำแหน่งของตัวชี้หลังการเคลื่อนไหว
4. Setsize: วิธีการนี้จะเปลี่ยนขนาดของข้อมูล ต้นแบบฟังก์ชันคือ:
ฟังก์ชั่น Setsize (NewSize: Longint); เสมือน;
นอกจากนี้ ยังมีการกำหนดวิธีการคงที่หลายวิธีในคลาส TStream:
1. ReadBuffer: ฟังก์ชั่นของวิธีนี้คือการอ่านข้อมูลจากตำแหน่งปัจจุบันในสตรีม ต้นแบบฟังก์ชันคือ:
กระบวนการ ReadBuffer (บัฟเฟอร์ var; นับ: Longint);
คำจำกัดความของพารามิเตอร์เหมือนกับที่อ่านด้านบน หมายเหตุ: เมื่อจำนวนไบต์ข้อมูลที่อ่านไม่เหมือนกับจำนวนไบต์ที่ต้องอ่าน จะมีการสร้างข้อยกเว้น EReadError
2. WriteBuffer: ฟังก์ชั่นของวิธีนี้คือการเขียนข้อมูลไปยังสตรีมที่ตำแหน่งปัจจุบัน ต้นแบบฟังก์ชันคือ:
ขั้นตอน WriteBuffer(var Buffer;Count:Longint);
คำจำกัดความของพารามิเตอร์เหมือนกับเขียนด้านบน หมายเหตุ: เมื่อจำนวนไบต์ข้อมูลที่เขียนไม่เหมือนกับจำนวนไบต์ที่ต้องเขียน จะมีการสร้างข้อยกเว้น EWriteError
3. CopyFrom: วิธีการนี้ใช้เพื่อคัดลอกสตรีมข้อมูลจากสตรีมอื่น ต้นแบบฟังก์ชันคือ:
ฟังก์ชั่น CopyFrom (ที่มา: TStream; Count: Longint): Longint;
พารามิเตอร์ Source คือสตรีมที่ให้ข้อมูล และ Count คือจำนวนไบต์ข้อมูลที่คัดลอก เมื่อ Count มากกว่า 0 CopyFrom จะคัดลอกจำนวนไบต์ของข้อมูลจากตำแหน่งปัจจุบันของพารามิเตอร์ Source เมื่อ Count เท่ากับ 0 CopyFrom จะตั้งค่าคุณสมบัติ Position ของพารามิเตอร์ Source เป็น 0 จากนั้นคัดลอกข้อมูลทั้งหมดของ Source ;
TStream มีคลาสที่ได้รับมาอื่นๆ ซึ่งคลาสที่ใช้กันมากที่สุดคือคลาส TFileStream หากต้องการใช้คลาส TFileStream เพื่อเข้าถึงไฟล์ คุณต้องสร้างอินสแตนซ์ก่อน คำชี้แจงมีดังนี้:
ตัวสร้างสร้าง (const Filename:string;Mode:Word);
ชื่อไฟล์คือชื่อไฟล์ (รวมถึงเส้นทาง) และโหมดพารามิเตอร์เป็นวิธีการเปิดไฟล์ซึ่งรวมถึงโหมดการเปิดไฟล์และโหมดการแชร์ค่าที่เป็นไปได้และความหมายมีดังนี้:
โหมดเปิด:
fmCreate: สร้างไฟล์ตามชื่อไฟล์ที่ระบุ หรือเปิดไฟล์หากมีอยู่แล้ว
fmOpenRead: เปิดไฟล์ที่ระบุในโหมดอ่านอย่างเดียว
fmOpenWrite: เปิดไฟล์ที่ระบุในโหมดเขียนอย่างเดียว
fmOpenReadWrite: เปิดไฟล์ที่ระบุเพื่อเขียน
โหมดการแชร์:
fmShareCompat: โหมดแชร์เข้ากันได้กับ FCB
fmShareExclusive: ไม่อนุญาตให้โปรแกรมอื่นเปิดไฟล์ไม่ว่าด้วยวิธีใดก็ตาม
fmShareDenyWrite: ไม่อนุญาตให้โปรแกรมอื่นเปิดไฟล์เพื่อเขียน
fmShareDenyRead: ไม่อนุญาตให้โปรแกรมอื่นเปิดไฟล์ในโหมดอ่าน
fmShareDenyNone: โปรแกรมอื่นสามารถเปิดไฟล์ได้ทุกทาง
TStream ยังมีคลาสที่ได้รับ TMemoryStream ซึ่งใช้บ่อยมากในแอปพลิเคชันจริง เรียกว่าสตรีมหน่วยความจำ ซึ่งหมายถึงการสร้างวัตถุสตรีมในหน่วยความจำ วิธีการและฟังก์ชันพื้นฐานเหมือนกับข้างต้น
ด้วยรากฐานข้างต้น เราก็สามารถเริ่มต้นเส้นทางการเขียนโปรแกรมของเราได้
-------------------------------------------------- -------------------------------------------------- -------------------------------
2. การใช้งานจริงประการที่หนึ่ง: การใช้สตรีมเพื่อสร้างตัวเข้ารหัสไฟล์ EXE, บันเดิล, ไฟล์ที่ขยายในตัว และโปรแกรมการติดตั้ง
ก่อนอื่นเรามาพูดถึงวิธีสร้างตัวเข้ารหัสไฟล์ EXE กันก่อน
หลักการของตัวเข้ารหัสไฟล์ EXE: สร้างไฟล์สองไฟล์ โดยไฟล์หนึ่งใช้เพื่อเพิ่มทรัพยากรให้กับไฟล์ EXE อื่น ซึ่งเรียกว่าโปรแกรมเสริม ไฟล์ EXE อื่นที่เพิ่มเข้ามาเรียกว่าไฟล์ส่วนหัว หน้าที่ของโปรแกรมนี้คือการอ่านไฟล์ที่เพิ่มเข้ามาในตัวมันเอง โครงสร้างไฟล์ EXE ใน Windows ค่อนข้างซับซ้อน และบางโปรแกรมก็มีการตรวจสอบความถูกต้องด้วย เมื่อพบว่ามีการเปลี่ยนแปลง พวกเขาจะคิดว่าติดไวรัสและปฏิเสธที่จะดำเนินการ เราจึงเพิ่มไฟล์ลงในโปรแกรมของเราเองเพื่อไม่ให้โครงสร้างไฟล์ต้นฉบับเปลี่ยนแปลงไป ก่อนอื่นมาเขียนฟังก์ชันเพิ่มกันก่อน หน้าที่ของฟังก์ชันนี้คือการเพิ่มไฟล์เป็นสตรีมที่ส่วนท้ายของไฟล์อื่น ฟังก์ชั่นมีดังนี้:
ฟังก์ชัน Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
เป้าหมายแหล่งที่มา: TFileStream;
MyFileSize:จำนวนเต็ม;
เริ่ม
พยายาม
ที่มา:=TFileStream.Create(SourceFile,fmOpenRead หรือ fmShareExclusive);
เป้าหมาย:=TFileStream.Create(TargetFile,fmOpenWrite หรือ fmShareExclusive);
พยายาม
Target.Seek(0,soFromEnd);//เพิ่มทรัพยากรต่อท้าย
Target.CopyFrom(แหล่งที่มา,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//คำนวณขนาดทรัพยากรและเขียนไว้ที่ส่วนท้ายของกระบวนการเสริม
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
ในที่สุด
เป้าหมายฟรี;
แหล่งที่มาฟรี;
จบ;
ยกเว้น
ผลลัพธ์:=เท็จ;
ออก;
จบ;
ผลลัพธ์:=จริง;
จบ;
ด้วยพื้นฐานข้างต้น เราควรเข้าใจฟังก์ชันนี้ได้อย่างง่ายดาย พารามิเตอร์ SourceFile คือไฟล์ที่จะเพิ่ม และพารามิเตอร์ TargetFile คือไฟล์เป้าหมายที่จะเพิ่ม ตัวอย่างเช่น หากต้องการเพิ่ม a.exe ไปที่ b.exe: Cjt_AddtoFile('a.exe',b.exe'); หากการเพิ่มสำเร็จ ระบบจะส่งคืน True มิฉะนั้นจะส่งคืน False
จากฟังก์ชันข้างต้น เราสามารถเขียนฟังก์ชัน read ที่ตรงกันข้ามได้:
ฟังก์ชัน Cjt_LoadFromFile(SourceFile,TargetFile:string):Boolean;
var
ที่มา:TFileStream;
เป้าหมาย: TMemoryStream;
MyFileSize:จำนวนเต็ม;
เริ่ม
พยายาม
เป้าหมาย:=TMemoryStream.Create;
ที่มา:=TFileStream.Create(SourceFile,fmOpenRead หรือ fmShareDenyNone);
พยายาม
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//อ่านขนาดทรัพยากร
Source.Seek(-MyFileSize,soFromEnd);//ค้นหาตำแหน่งของทรัพยากร
Target.CopyFrom(Source,MyFileSize-sizeof(MyFileSize));//ลบทรัพยากร
Target.SaveToFile(TargetFile);//บันทึกลงไฟล์
ในที่สุด
เป้าหมายฟรี;
แหล่งที่มาฟรี;
จบ;
ยกเว้น
ผลลัพธ์:=เท็จ;
ออก;
จบ;
ผลลัพธ์:=จริง;
จบ;
พารามิเตอร์ SourceFile คือชื่อของไฟล์ที่มีการเพิ่มไฟล์ และพารามิเตอร์ TargetFile คือชื่อของไฟล์เป้าหมายที่บันทึกไว้หลังจากไฟล์ถูกนำออกไป ตัวอย่างเช่น Cjt_LoadFromFile('b.exe','a.txt'); นำไฟล์ออกมาใน b.exe และบันทึกเป็น a.txt หากการแยกข้อมูลสำเร็จ จะคืนค่า True มิฉะนั้นจะส่งคืนค่า False
เปิด Delphi สร้างโปรเจ็กต์ใหม่ และใส่ตัวควบคุม Edit1 และปุ่มสองปุ่มบนหน้าต่าง: Button1 และ Button2 คุณสมบัติคำอธิบายภาพของปุ่มถูกตั้งค่าเป็น "ตกลง" และ "ยกเลิก" ตามลำดับ เขียนโค้ดในเหตุการณ์ Click ของ Button1:
var S: สตริง;
เริ่ม
S:=ChangeFileExt(application.ExeName,'.Cjt');
ถ้า Edit1.Text='790617' แล้ว
เริ่ม
Cjt_LoadFromFile(Application.ExeName,S);
{นำไฟล์ออกและบันทึกไว้ในเส้นทางปัจจุบันแล้วตั้งชื่อเป็น "ไฟล์ต้นฉบับ.Cjt"}
Winexec(pchar(S),SW_Show); {เรียกใช้ "ไฟล์ต้นฉบับ.Cjt"}
Application.Terminate;{ออกจากโปรแกรม}
จบ
อื่น
Application.MessageBox('รหัสผ่านไม่ถูกต้อง กรุณากรอกใหม่!', 'รหัสผ่านไม่ถูกต้อง', MB_ICONERROR+MB_OK);
คอมไพล์โปรแกรมนี้และเปลี่ยนชื่อไฟล์ EXE เป็น head.exe สร้างไฟล์ข้อความใหม่ head.rc ด้วยเนื้อหา: head exefile head.exe จากนั้นคัดลอกไปยังไดเร็กทอรี BIN ของ Delphi รันคำสั่ง Dos Brcc32.exe head.rc และไฟล์ head.res จะถูกสร้างขึ้น file คือ เก็บไฟล์ทรัพยากรที่เราต้องการไว้ก่อน
ไฟล์ส่วนหัวของเราถูกสร้างขึ้นแล้ว มาสร้างโปรแกรม Add-in กัน
สร้างโครงการใหม่และวางการควบคุมต่อไปนี้: คุณสมบัติ Edit, Opendialog และ Caption ของ Button1 ทั้งสองได้รับการตั้งค่าเป็น "Select File" และ "Encryption" ตามลำดับ เพิ่มประโยคในซอร์สโปรแกรม: {$R head.res} และคัดลอกไฟล์ head.res ไปยังไดเร็กทอรีปัจจุบันของโปรแกรม ด้วยวิธีนี้ head.exe ในตอนนี้จะถูกคอมไพล์พร้อมกับโปรแกรม
เขียนโค้ดในเหตุการณ์ Cilck ของ Button1:
ถ้า OpenDialog1.Execute ให้แก้ไข1.Text:=OpenDialog1.FileName;
เขียนโค้ดในเหตุการณ์ Cilck ของ Button2:
var S:สตริง;
เริ่ม
S:=ExtractFilePath(แก้ไข1.ข้อความ);
ถ้า ExtractRes('exefile','head',S+'head.exe') แล้ว
ถ้า Cjt_AddtoFile(Edit1.Text,S+'head.exe') แล้ว
ถ้า DeleteFile(Edit1.Text) แล้ว
ถ้า RenameFile(S+'head.exe',Edit1.Text) แล้ว
Application.MessageBox('การเข้ารหัสไฟล์สำเร็จ!','ข้อความ',MB_ICONINFORMATION+MB_OK)
อื่น
เริ่ม
ถ้า FileExists(S+'head.exe') แล้วก็ DeleteFile(S+'head.exe');
Application.MessageBox('การเข้ารหัสไฟล์ล้มเหลว!','ข้อความ',MB_ICONINFORMATION+MB_OK)
จบ;
จบ;
ในหมู่พวกเขา ExtractRes เป็นฟังก์ชันที่กำหนดเองซึ่งใช้ในการแยก head.exe ออกจากไฟล์ทรัพยากร
ฟังก์ชัน ExtractRes(ResType, ResName, ResNewName : String):boolean;
var
Res : TResourceStream;
เริ่ม
พยายาม
Res := TResourceStream.Create (คำแนะนำ, Resname, Pchar (ResType));
พยายาม
Res.SavetoFile(ResNewName);
ผลลัพธ์:=จริง;
ในที่สุด
Res.ฟรี;
จบ;
ยกเว้น
ผลลัพธ์:=เท็จ;
จบ;
จบ;
หมายเหตุ: ฟังก์ชั่นของเราข้างต้นเพียงเพิ่มไฟล์หนึ่งต่อท้ายไฟล์อื่น ในแอปพลิเคชันจริง สามารถเปลี่ยนเพื่อเพิ่มหลายไฟล์ได้ ตราบใดที่ที่อยู่ออฟเซ็ตถูกกำหนดตามขนาดและจำนวนจริง ตัวอย่างเช่น ตัวรวมไฟล์จะเพิ่มโปรแกรมตั้งแต่สองโปรแกรมขึ้นไปลงในไฟล์ส่วนหัว หลักการของโปรแกรมและตัวติดตั้งแบบขยายตัวเองจะเหมือนกัน แต่มีการบีบอัดที่มากกว่า ตัวอย่างเช่น เราสามารถอ้างอิงหน่วย LAH บีบอัดสตรีมแล้วเพิ่มเข้าไป เพื่อให้ไฟล์มีขนาดเล็กลง เพียงขยายขนาดก่อนที่จะอ่านออก นอกจากนี้ ตัวอย่างของตัวเข้ารหัส EXE ในบทความยังมีข้อบกพร่องอยู่หลายประการ เช่น รหัสผ่านได้รับการแก้ไขเป็น "790617" และหลังจากนำ EXE ออกและเปิดใช้งานแล้ว ควรลบออกหลังจากทำงานเสร็จแล้ว เป็นต้น . ผู้อ่านสามารถแก้ไขได้ด้วยตนเอง
-------------------------------------------------- -------------------------------------------------- -------------------
3. การใช้งานจริง 2: การใช้สตรีมเพื่อสร้างบัตรอวยพรอิเล็กทรอนิกส์ที่สามารถเรียกใช้งานได้
เรามักจะเห็นซอฟต์แวร์สร้างการ์ดอวยพรอิเล็กทรอนิกส์ที่ช่วยให้คุณสามารถเลือกรูปภาพได้ด้วยตัวเอง จากนั้นซอฟต์แวร์จะสร้างไฟล์ปฏิบัติการ EXE ให้กับคุณ เมื่อคุณเปิดการ์ดอวยพร รูปภาพจะปรากฏขึ้นขณะเล่นเพลง ตอนนี้เราได้เรียนรู้การดำเนินการของสตรีมแล้ว เราก็สามารถสร้างได้เช่นกัน
ในกระบวนการเพิ่มรูปภาพ เราสามารถใช้ Cjt_AddtoFile ก่อนหน้าได้โดยตรง และสิ่งที่เราต้องทำตอนนี้คือวิธีอ่านและแสดงรูปภาพ เราสามารถใช้ Cjt_LoadFromFile ก่อนหน้าเพื่ออ่านรูปภาพก่อน บันทึกเป็นไฟล์ แล้วจึงโหลดเข้าไป อย่างไรก็ตาม มีวิธีที่ง่ายกว่าคืออ่านสตรีมไฟล์โดยตรงแล้วแสดงด้วยเครื่องมืออันทรงพลังของสตรีม ทุกสิ่งกลายเป็นเรื่องง่าย
รูปภาพที่ได้รับความนิยมมากที่สุดในปัจจุบันคือรูปแบบ BMP และ JPG ตอนนี้เราจะเขียนฟังก์ชันการอ่านและการแสดงผลสำหรับรูปภาพทั้งสองประเภทนี้
ฟังก์ชัน Cjt_BmpLoad(ImgBmp:TImage;SourceFile:String):บูลีน;
var
ที่มา:TFileStream;
MyFileSize:จำนวนเต็ม;
เริ่ม
ที่มา:=TFileStream.Create(SourceFile,fmOpenRead หรือ fmShareDenyNone);
พยายาม
พยายาม
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//อ่านทรัพยากร
Source.Seek(-MyFileSize,soFromEnd);//ค้นหาตำแหน่งเริ่มต้นของทรัพยากร
ImgBmp.Picture.Bitmap.LoadFromStream (แหล่งที่มา);
ในที่สุด
แหล่งที่มาฟรี;
จบ;
ยกเว้น
ผลลัพธ์:=เท็จ;
ออก;
จบ;
ผลลัพธ์:=จริง;
จบ;
ด้านบนนี้เป็นฟังก์ชั่นสำหรับอ่านภาพ BMP และต่อไปนี้เป็นฟังก์ชั่นสำหรับอ่านภาพ JPG เนื่องจากมีการใช้หน่วย JPG จึงต้องเพิ่มประโยค: ใช้ jpeg ลงในโปรแกรม
ฟังก์ชัน Cjt_JpgLoad(JpgImg:Timage;SourceFile:String):Boolean;
var
ที่มา:TFileStream;
MyFileSize:จำนวนเต็ม;
Myjpg: TJpegImage;
เริ่ม
พยายาม
Myjpg:= TJpegImage.Create;
ที่มา:=TFileStream.Create(SourceFile,fmOpenRead หรือ fmShareDenyNone);
พยายาม
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));
Source.Seek(-MyFileSize,soFromEnd);
Myjpg.LoadFromStream(ที่มา);
JpgImg.Picture.Bitmap.Assign (Myjpg);
ในที่สุด
แหล่งที่มาฟรี;
Myjpg.free;
จบ;
ยกเว้น
ผลลัพธ์:=เท็จ;
ออก;
จบ;
ผลลัพธ์:=จริง;
จบ;
ด้วยฟังก์ชันทั้งสองนี้ เราสามารถสร้างโปรแกรมอ่านข้อมูลได้ ลองใช้รูปภาพ BMP เป็นตัวอย่าง:
เรียกใช้ Delphi สร้างโปรเจ็กต์ใหม่ และวางตัวควบคุมการแสดงรูปภาพ Image1 เพียงเขียนประโยคต่อไปนี้ในเหตุการณ์สร้างของหน้าต่าง:
Cjt_BmpLoad (รูปภาพ 1, Application.ExeName);
นี่คือไฟล์ส่วนหัว จากนั้นเราใช้วิธีก่อนหน้าเพื่อสร้างไฟล์ทรัพยากร head.res
ตอนนี้เราสามารถเริ่มสร้างโปรแกรมเสริมของเราได้แล้ว รหัสทั้งหมดมีดังนี้:
หน่วย หน่วยที่ 1;
อินเตอร์เฟซ
การใช้งาน
Windows, ข้อความ, SysUtils, คลาส, กราฟิก, การควบคุม, แบบฟอร์ม, กล่องโต้ตอบ,
ExtCtrls, StdCtrls, ExtDlgs;
พิมพ์
TForm1 = คลาส (TForm)
แก้ไข 1: TEdit;
Button1: T ปุ่ม;
Button2: T ปุ่ม;
OpenPictureDialog1: TOpenPictureDialog;
ขั้นตอน FormCreate (ผู้ส่ง: TObject);
ขั้นตอน Button1Click (ผู้ส่ง: TObject);
ขั้นตอน Button2Click (ผู้ส่ง: TObject);
ส่วนตัว
ฟังก์ชัน ExtractRes(ResType, ResName, ResNewName : String):boolean;
ฟังก์ชัน Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
{ประกาศส่วนตัว}
สาธารณะ
{ประกาศสาธารณะ}
จบ;
var
แบบฟอร์ม 1: TForm1;
การดำเนินการ
{$R *.DFM}
ฟังก์ชัน TForm1.ExtractRes (ResType, ResName, ResNewName : String): บูลีน;
var
Res : TResourceStream;
เริ่ม
พยายาม
Res := TResourceStream.Create (คำแนะนำ, Resname, Pchar (ResType));
พยายาม
Res.SavetoFile(ResNewName);
ผลลัพธ์:=จริง;
ในที่สุด
Res.ฟรี;
จบ;
ยกเว้น
ผลลัพธ์:=เท็จ;
จบ;
จบ;
ฟังก์ชัน TForm1.Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
เป้าหมายแหล่งที่มา: TFileStream;
MyFileSize:จำนวนเต็ม;
เริ่ม
พยายาม
ที่มา:=TFileStream.Create(SourceFile,fmOpenRead หรือ fmShareExclusive);
เป้าหมาย:=TFileStream.Create(TargetFile,fmOpenWrite หรือ fmShareExclusive);
พยายาม
Target.Seek(0,soFromEnd);//เพิ่มทรัพยากรต่อท้าย
Target.CopyFrom(แหล่งที่มา,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//คำนวณขนาดทรัพยากรและเขียนไว้ที่ส่วนท้ายของกระบวนการเสริม
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
ในที่สุด
เป้าหมายฟรี;
แหล่งที่มาฟรี;
จบ;
ยกเว้น
ผลลัพธ์:=เท็จ;
ออก;
จบ;
ผลลัพธ์:=จริง;
จบ;
ขั้นตอน TForm1.FormCreate (ผู้ส่ง: TObject);
เริ่ม
Caption:='โปรแกรมสาธิต Bmp2Exe ผู้แต่ง: Chen Jingtao';
แก้ไข1.ข้อความ:='';
OpenPictureDialog1.DefaultExt := GraphicExtension(TBitmap);
OpenPictureDialog1.Filter := GraphicFilter (TBitmap);
Button1.Caption:='เลือกรูปภาพ BMP';
Button2.Caption:='สร้าง EXE';
จบ;
ขั้นตอน TForm1.Button1Click (ผู้ส่ง: TObject);
เริ่ม
ถ้า OpenPictureDialog1.Execute แล้ว
แก้ไข1.ข้อความ:=OpenPictureDialog1.FileName;
จบ;
ขั้นตอน TForm1.Button2Click (ผู้ส่ง: TObject);
var
อุณหภูมิศีรษะ:สตริง;
เริ่ม
ถ้าไม่ใช่ FileExists (Edit1.Text) แล้ว
เริ่ม
Application.MessageBox('ไม่มีไฟล์ภาพ BMP โปรดเลือกอีกครั้ง!','ข้อความ',MB_ICONINFORMATION+MB_OK)
ออก;
จบ;
HeadTemp:=ChangeFileExt(Edit1.Text,'.exe');
ถ้า ExtractRes('exefile','head',HeadTemp) แล้ว
ถ้า Cjt_AddtoFile(Edit1.Text,HeadTemp) แล้ว
Application.MessageBox('สร้างไฟล์ EXE สำเร็จ!','ข้อความ',MB_ICONINFORMATION+MB_OK)
อื่น
เริ่ม
ถ้า FileExists(HeadTemp) แล้ว DeleteFile(HeadTemp);
Application.MessageBox('การสร้างไฟล์ EXE ล้มเหลว!','ข้อความ',MB_ICONINFORMATION+MB_OK)
จบ;
จบ;
จบ.
แล้วไงล่ะ? มันน่าทึ่งมาก :) ทำให้อินเทอร์เฟซของโปรแกรมสวยงามยิ่งขึ้นและเพิ่มฟังก์ชั่นบางอย่างแล้วคุณจะพบว่ามันไม่ได้ด้อยไปกว่าซอฟต์แวร์ที่ต้องลงทะเบียนมากนัก
-------------------------------------------------- -------------------------------------------------- -------------------------------
การประยุกต์ใช้ในทางปฏิบัติประการที่สาม: ใช้สตรีมเพื่อสร้าง OICQ ของคุณเอง
OICQ เป็นซอฟต์แวร์การสื่อสารแบบเรียลไทม์ออนไลน์ที่พัฒนาโดยบริษัท Tencent เซินเจิ้น และมีฐานผู้ใช้จำนวนมากในประเทศจีน อย่างไรก็ตาม OICQ จะต้องเชื่อมต่อกับอินเทอร์เน็ตและลงชื่อเข้าใช้เซิร์ฟเวอร์ของ Tencent ก่อนจึงจะสามารถใช้งานได้ ดังนั้นเราจึงสามารถเขียนเองและใช้ในเครือข่ายท้องถิ่นได้
OICQ ใช้โปรโตคอล UDP ซึ่งเป็นโปรโตคอลไร้การเชื่อมต่อ กล่าวคือ ฝ่ายสื่อสารสามารถส่งข้อมูลได้โดยไม่ต้องสร้างการเชื่อมต่อ ดังนั้นประสิทธิภาพจึงค่อนข้างสูง การควบคุม NMUDP ของ FastNEt ที่มาพร้อมกับ Delphi นั้นเป็นการควบคุมดาตาแกรมผู้ใช้ของโปรโตคอล UDP อย่างไรก็ตาม ควรสังเกตว่าหากคุณใช้ตัวควบคุมนี้ คุณต้องออกจากโปรแกรมก่อนปิดคอมพิวเตอร์ เนื่องจากตัวควบคุม TNMXXX มี BUG ThreadTimer ที่ใช้โดย PowerSocket ซึ่งเป็นพื้นฐานของการควบคุม nm ทั้งหมด ใช้หน้าต่างที่ซ่อนอยู่ (คลาส TmrWindowClass) เพื่อจัดการกับข้อบกพร่อง
เกิดอะไรขึ้น:
Psock::TThreadTimer::WndProc (var msg:TMessage)
ถ้า msg.message=WM_TIMER แล้ว
เขาจัดการเอง
msg.ผลลัพธ์:=0
อื่น
msg.result:=DefWindowProc(0,....)
จบ
ปัญหาคือว่าเมื่อเรียก DefWindowProc พารามิเตอร์ HWND ที่ส่งจะเป็นค่าคงที่ 0 ดังนั้นในความเป็นจริง DefWindowProc ไม่สามารถทำงานได้ การเรียกไปยังข้อความอินพุตใด ๆ จะส่งคืน 0 รวมถึง WM_QUERYENDsession ด้วย ดังนั้น windows จึงไม่สามารถออกได้ เนื่องจากการเรียก DefWindowProc ผิดปกติ ที่จริงแล้ว ยกเว้น WM_TIMER ข้อความอื่นๆ ที่ประมวลผลโดย DefWindowProc จึงไม่ถูกต้อง
วิธีแก้ไขอยู่ใน PSock.pas
ภายใน TThreadTimer.Wndproc
ผลลัพธ์ := DefWindowProc( 0, ข่าวสารเกี่ยวกับ, WPARAM, LPARAM );
เปลี่ยนเป็น:
ผลลัพธ์ := DefWindowProc( FWindowHandle, Msg, WPARAM, LPARAM );
OICQ เวอร์ชันต่ำในช่วงต้นก็ประสบปัญหานี้เช่นกัน หากไม่ได้ปิด OICQ หน้าจอจะกะพริบครู่หนึ่งแล้วกลับมาอีกครั้งเมื่อปิดคอมพิวเตอร์
เอาล่ะ เพื่อไม่ให้เป็นการเสียเวลา มาเขียน OICQ ของเรากันดีกว่า นี่เป็นตัวอย่างที่มาพร้อมกับ Delphi :)
สร้างโปรเจ็กต์ใหม่ ลากตัวควบคุม NMUDP จากแผง FASTNET ไปที่หน้าต่าง จากนั้นวางการแก้ไขสามรายการชื่อ Editip, EditPort, EditMyTxt, สามปุ่ม BtSend, BtClear, BtSave, MEMOMemoReceive, SaveDialog และแถบสถานะ เมื่อผู้ใช้คลิก BtSend วัตถุสตรีมหน่วยความจำจะถูกสร้างขึ้น ข้อมูลข้อความที่จะส่งจะถูกเขียนลงในสตรีมหน่วยความจำ จากนั้น NMUDP จะส่งกระแสข้อมูลออก เมื่อ NMUDP ได้รับข้อมูล เหตุการณ์ DataReceived จะถูกทริกเกอร์ ที่นี่ เราจะแปลงสตรีมที่ได้รับเป็นข้อมูลตัวอักษรแล้วแสดงข้อมูลดังกล่าว
หมายเหตุ: อย่าลืมปล่อย (ฟรี) ออบเจ็กต์สตรีมทั้งหมดหลังจากที่สร้างและใช้งานแล้ว จริงๆ แล้ว destructor ควรเป็น Destroy อย่างไรก็ตาม หากการสร้างสตรีมล้มเหลว การใช้ Destroy จะสร้างข้อยกเว้นขึ้นมา จะตรวจสอบก่อนว่า หากสร้างสตรีมไม่สำเร็จ จะปล่อยเฉพาะเมื่อมีการสร้างแล้วเท่านั้น จึงจะปลอดภัยกว่าถ้าใช้แบบฟรี
ในโปรแกรมนี้เราใช้การควบคุม NMUDP ซึ่งมีคุณสมบัติที่สำคัญหลายประการ RemoteHost แสดงถึง IP หรือชื่อคอมพิวเตอร์ของคอมพิวเตอร์ระยะไกล และ LocalPort คือพอร์ตภายในเครื่อง ซึ่งจะตรวจสอบว่ามีข้อมูลขาเข้าเป็นหลักหรือไม่ RemotePort เป็นพอร์ตระยะไกล และข้อมูลจะถูกส่งผ่านพอร์ตนี้เมื่อส่งข้อมูล การทำความเข้าใจสิ่งเหล่านี้สามารถเข้าใจโปรแกรมของเราได้แล้ว
รหัสทั้งหมดมีดังนี้:
หน่วย หน่วยที่ 1;
อินเตอร์เฟซ
การใช้งาน
Windows, ข้อความ, SysUtils, คลาส, กราฟิก, การควบคุม, แบบฟอร์ม, ไดอะล็อก, StdCtrls, ComCtrls, NMUDP;
พิมพ์
TForm1 = คลาส (TForm)
NMUDP1: TNMUDP;
แก้ไข IP: TEdit;
แก้ไขพอร์ต: TEdit;
แก้ไข MyTxt: TEdit;
MemoReceive: TMemo;
BtSend: TButton;
BtClear: TButton;
BtSave: TButton;
StatusBar1: TStatusBar;
SaveDialog1: TSaveDialog;
ขั้นตอน BtSendClick (ผู้ส่ง: TObject);
ขั้นตอน NMUDP1DataReceived (ผู้ส่ง: TComponent; NumberBytes: จำนวนเต็ม;
FromIP: สตริง; พอร์ต: จำนวนเต็ม);
ขั้นตอน NMUDP1InvalidHost (จัดการ var: บูลีน);
ขั้นตอน NMUDP1DataSend (ผู้ส่ง: TObject);
ขั้นตอน FormCreate (ผู้ส่ง: TObject);
ขั้นตอน BtClearClick (ผู้ส่ง: TObject);
ขั้นตอน BtSaveClick (ผู้ส่ง: TObject);
ขั้นตอน EditMyTxtKeyPress (ผู้ส่ง: TObject; var Key: Char);
ส่วนตัว
{ประกาศส่วนตัว}
สาธารณะ
{ประกาศสาธารณะ}
จบ;
var
แบบฟอร์ม 1: TForm1;
การดำเนินการ
{$R *.DFM}
ขั้นตอน TForm1.BtSendClick (ผู้ส่ง: TObject);
var
สตรีมของฉัน: TMemoryStream;
MySendTxt: สตริง;
นำเข้า, icode: จำนวนเต็ม;
เริ่ม
Val(EditPort.Text,นำเข้า,icode);
ถ้า icode<>0 แล้ว
เริ่ม
Application.MessageBox('พอร์ตต้องเป็นตัวเลข กรุณากรอกใหม่!','ข้อความ',MB_ICONINFORMATION+MB_OK);
ออก;
จบ;
NMUDP1.RemoteHost := แก้ไข IP.Text {โฮสต์ระยะไกล}
NMUDP1.LocalPort:=นำเข้าพอร์ต;
NMUDP1.RemotePort := {พอร์ตระยะไกล}
MySendTxt := แก้ไข MyTxt.Text;
MyStream := TMemoryStream.Create {สร้างกระแส}
พยายาม
MyStream.Write(MySendTxt[1], ความยาว(EditMyTxt.Text));{เขียนข้อมูล}
NMUDP1.SendStream(MyStream); {ส่งกระแสข้อมูล}
ในที่สุด
MyStream.Free {ปล่อยสตรีม}
จบ;
จบ;
ขั้นตอน TForm1.NMUDP1DataReceived (ผู้ส่ง: TComponent;
NumberBytes: จำนวนเต็ม; FromIP: สตริง;
var
สตรีมของฉัน: TMemoryStream;
MyReciveTxt: สตริง;
เริ่ม
MyStream := TMemoryStream.Create {สร้างกระแส}
พยายาม
NMUDP1.ReadStream (MyStream); {รับกระแส}
SetLength(MyReciveTxt,NumberBytes);{NumberBytes คือจำนวนไบต์ที่ได้รับ}
MyStream.Read(MyReciveTxt[1],NumberBytes);{อ่านข้อมูล}
MemoReceive.Lines.Add('ได้รับข้อมูลจากโฮสต์ '+FromIP+':'+MyReciveTxt);
ในที่สุด
MyStream.Free {ปล่อยสตรีม}
จบ;
จบ;
ขั้นตอน TForm1.NMUDP1InvalidHost (จัดการ var: บูลีน);
เริ่ม
Application.MessageBox('ที่อยู่ IP ของอีกฝ่ายไม่ถูกต้อง โปรดป้อนใหม่!','ข้อความ',MB_ICONINFORMATION+MB_OK);
จบ;
ขั้นตอน TForm1.NMUDP1DataSend (ผู้ส่ง: TObject);
เริ่ม
StatusBar1.SimpleText:='ส่งข้อความเรียบร้อยแล้ว!';
จบ;
ขั้นตอน TForm1.FormCreate (ผู้ส่ง: TObject);
เริ่ม
แก้ไข IP.Text:='127.0.0.1';
แก้ไขพอร์ตข้อความ:='8868';
BtSend.Caption:='ส่ง';
BtClear.Caption:='ล้างประวัติการสนทนา';
BtSave.Caption:='บันทึกประวัติการแชท';
MemoReceive.ScrollBars:=ssBoth;
MemoReceive.เคลียร์;
EditMyTxt.Text:='ป้อนข้อมูลที่นี่แล้วคลิกส่ง';
StatusBar1.SimplePanel:=จริง;
จบ;
ขั้นตอน TForm1.BtClearClick (ผู้ส่ง: TObject);
เริ่ม
MemoReceive.เคลียร์;
จบ;
ขั้นตอน TForm1.BtSaveClick (ผู้ส่ง: TObject);
เริ่ม
ถ้า SaveDialog1.Execute แล้ว MemoReceive.Lines.SaveToFile(SaveDialog1.FileName);
จบ;
ขั้นตอน TForm1.EditMyTxtKeyPress (ผู้ส่ง: TObject; var Key: Char);
เริ่ม
ถ้า Key=#13 แล้ว BtSend.Click;
จบ;
จบ.
โปรแกรมข้างต้นยังตามหลัง OICQ อยู่มาก เนื่องจาก OICQ ใช้วิธีการสื่อสารผ่าน Socket5 เมื่อออนไลน์จะดึงข้อมูลเพื่อนและสถานะออนไลน์จากเซิร์ฟเวอร์ก่อน เมื่อส่งหมดเวลา จะบันทึกข้อมูลบนเซิร์ฟเวอร์ก่อน รออีกฝ่ายออนไลน์ในครั้งถัดไป จากนั้นจึงส่งแล้วลบข้อมูล การสำรองข้อมูลของเซิร์ฟเวอร์ คุณสามารถปรับปรุงโปรแกรมนี้ตามแนวคิดที่คุณได้เรียนรู้ก่อนหน้านี้ ตัวอย่างเช่น เพิ่มตัวควบคุม NMUDP เพื่อจัดการสถานะออนไลน์ ข้อมูลที่ส่งจะถูกแปลงเป็นรหัส ASCII ก่อนสำหรับการดำเนินการและจะเพิ่มข้อมูลส่วนหัวในภายหลัง การรับข้อมูลไม่ว่าส่วนหัวของข้อมูลจะถูกหรือไม่ก็ตามข้อมูลจะถูกถอดรหัสและแสดงเฉพาะเมื่อถูกต้องเท่านั้นจึงช่วยเพิ่มความปลอดภัยและการรักษาความลับ
นอกจากนี้ ข้อดีอีกประการหนึ่งของโปรโตคอล UDP ก็คือสามารถออกอากาศได้ ซึ่งหมายความว่าใครก็ตามที่อยู่ในกลุ่มเครือข่ายเดียวกันสามารถรับข้อมูลได้โดยไม่ต้องระบุที่อยู่ IP ที่เฉพาะเจาะจง โดยทั่วไปส่วนเครือข่ายจะแบ่งออกเป็นสามประเภท: A, B และ C
1~126.XXX.XXX.XXX (เครือข่ายคลาส A): ที่อยู่การออกอากาศคือ XXX.255.255.255
128~191.XXX.XXX.XXX (เครือข่ายคลาส B): ที่อยู่การออกอากาศคือ XXX.XXX.255.255
192~254.XXX.XXX.XXX (เครือข่ายประเภท C): ที่อยู่การออกอากาศคือ XXX.XXX.XXX.255
ตัวอย่างเช่น หากคอมพิวเตอร์สามเครื่องคือ 192.168.0.1, 192.168.0.10 และ 192.168.0.18 เมื่อส่งข้อมูล คุณจะต้องระบุที่อยู่ IP 192.168.0.255 เท่านั้นจึงจะออกอากาศได้ ด้านล่างนี้เป็นฟังก์ชันที่แปลง IP เป็นการออกอากาศ IP ใช้เพื่อปรับปรุง OICQ ของคุณเอง
ฟังก์ชัน Trun_ip(S:สตริง):สตริง;
var s1,s2,s3,ss,sss,หัว: string;
n,m:จำนวนเต็ม;
เริ่ม
เอสเอส:=ส;
n:=pos('.',s);
s1:=คัดลอก(s,1,n);
ม:=ความยาว(s1);
ลบ(s,1,m);
หัวหน้า:=copy(s1,1,(length(s1)-1));
n:=pos('.',s);
s2:=คัดลอก(s,1,n);
ม:=ความยาว(s2);
ลบ(s,1,m);
n:=pos('.',s);
s3:=คัดลอก(s,1,n);
ม:=ความยาว(s3);
ลบ(s,1,m);
เอสเอส:=เอสเอส;
ถ้า strtoint(Head) ใน [1..126] ดังนั้น ss:=s1+'255.255.255'; //1~126.255.255.255 (เครือข่ายคลาส A)
ถ้า strtoint(Head) ใน [128..191] ดังนั้น ss:=s1+s2+'255.255';//128~191.XXX.255.255 (เครือข่ายคลาส B)
ถ้า strtoint(Head) ใน [192..254] ดังนั้น ss:=s1+s2+s3+'255'; //192~254.XXX.XXX.255 (เครือข่ายหมวดหมู่)
ผลลัพธ์:=ss;
จบ;
-------------------------------------------------- -------------------------------------------------- -------------------------------
5. การใช้งานจริง 4: การใช้สตรีมเพื่อส่งภาพหน้าจอผ่านเครือข่าย
คุณน่าจะได้เห็นโปรแกรมการจัดการเครือข่ายมากมาย หนึ่งในหน้าที่ของโปรแกรมดังกล่าวคือการตรวจสอบหน้าจอของคอมพิวเตอร์ระยะไกล ในความเป็นจริงสิ่งนี้สามารถทำได้โดยใช้การดำเนินการสตรีม ด้านล่างเราจะยกตัวอย่าง ตัวอย่างนี้แบ่งออกเป็นสองโปรแกรม โปรแกรมหนึ่งคือเซิร์ฟเวอร์ และอีกโปรแกรมหนึ่งคือไคลเอนต์ หลังจากคอมไพล์โปรแกรมแล้ว สามารถใช้งานได้โดยตรงบนเครื่องเดียว เครือข่ายท้องถิ่น หรืออินเทอร์เน็ต มีการให้ความเห็นที่สอดคล้องกันในโปรแกรม เราจะทำการวิเคราะห์โดยละเอียดในภายหลัง
สร้างโครงการใหม่และลากตัวควบคุม ServerSocket ไปที่หน้าต่างบนแผงอินเทอร์เน็ต การควบคุมนี้ใช้เป็นหลักในการตรวจสอบไคลเอ็นต์และสร้างการเชื่อมต่อและการสื่อสารกับไคลเอ็นต์ หลังจากตั้งค่าพอร์ตการฟังแล้ว ให้เรียกใช้วิธี Open หรือ Active:=True เพื่อเริ่มทำงาน หมายเหตุ: ไม่เหมือนกับ NMUDP ก่อนหน้านี้ เมื่อซ็อกเก็ตเริ่มฟังแล้ว พอร์ตจะไม่สามารถเปลี่ยนได้ หากคุณต้องการเปลี่ยน คุณต้องเรียกปิดก่อนหรือตั้งค่า Active เป็น False มิฉะนั้นจะเกิดข้อยกเว้น นอกจากนี้หากพอร์ตเปิดอยู่แล้วพอร์ตนี้ก็จะไม่สามารถใช้งานได้อีกต่อไป ดังนั้นคุณไม่สามารถรันโปรแกรมอีกครั้งก่อนที่จะออก มิฉะนั้นจะเกิดข้อยกเว้น นั่นคือ หน้าต่างข้อผิดพลาดจะปรากฏขึ้น ในการใช้งานจริง สามารถหลีกเลี่ยงข้อผิดพลาดได้โดยการพิจารณาว่าโปรแกรมถูกรันและออกหรือไม่หากโปรแกรมนั้นรันอยู่แล้ว
เมื่อข้อมูลจากไคลเอ็นต์ถูกส่งผ่าน เหตุการณ์ ServerSocket1ClientRead จะถูกทริกเกอร์ และเราสามารถประมวลผลข้อมูลที่ได้รับได้ที่นี่ ในโปรแกรมนี้ ส่วนใหญ่จะได้รับข้อมูลตัวละครที่ลูกค้าส่งมาและดำเนินการที่เกี่ยวข้องตามข้อตกลงก่อนหน้า
รหัสโปรแกรมทั้งหมดเป็นดังนี้:
หน่วย Unit1;{โปรแกรมเซิร์ฟเวอร์}
อินเตอร์เฟซ
การใช้งาน
Windows, ข้อความ, SysUtils, คลาส, กราฟิก, การควบคุม, แบบฟอร์ม, ไดอะล็อก, JPEG, ExtCtrls, ScktComp;
พิมพ์
TForm1 = คลาส (TForm)
ServerSocket1: TServerSocket;
ขั้นตอน ServerSocket1ClientRead (ผู้ส่ง: TObject; ซ็อกเก็ต: TCustomWinSocket);
ขั้นตอน FormCreate (ผู้ส่ง: TObject);
ขั้นตอน FormClose (ผู้ส่ง: TObject; var Action: TCloseAction);
ส่วนตัว
ขั้นตอน Cjt_GetScreen (var Mybmp: TBitmap; DrawCur: Boolean);
{ฟังก์ชั่นจับภาพหน้าจอแบบกำหนดเอง DrawCur ระบุว่าจะจับภาพเมาส์หรือไม่}
{ประกาศส่วนตัว}
สาธารณะ
{ประกาศสาธารณะ}
จบ;
var
แบบฟอร์ม 1: TForm1;
MyStream: TMemorystream; {วัตถุสตรีมหน่วยความจำ}
การดำเนินการ
{$R *.DFM}
ขั้นตอน TForm1.Cjt_GetScreen (var Mybmp: TBitmap; DrawCur: Boolean);
var
Cursorx, Cursory: จำนวนเต็ม;
กระแสตรง:hdc;
มายแคน: Tcanvas;
ร: ตรง;
DrawPos: TPoint;
เคอร์เซอร์ของฉัน: TIcon;
hld: hwnd;
Threadld: dword;
mp: tpoint;
pIconInfo: TIconInfo;
เริ่ม
Mybmp := Tbitmap.Create; {สร้าง BMPMAP}
Mycan := TCanvas.Create; {จับภาพหน้าจอ}
dc := GetWindowDC(0);
พยายาม
Mycan.Handle := dc;
R := Rect(0, 0, screen.Width, screen.Height);
Mybmp.Width := R.Right;
Mybmp.Height := R.Bottom;
Mybmp.Canvas.CopyRect(R, Mycan, R);
ในที่สุด
ปล่อยDC(0, DC);
จบ;
Mycan.Handle := 0;
Mycan.ฟรี;
ถ้า DrawCur แล้ว {วาดภาพเมาส์}
เริ่ม
GetCursorPos(DrawPos);
MyCursor := TIcon สร้าง;
getcursorpos(mp);
hld := WindowFromPoint(mp);
Threadld := GetWindowThreadProcessId(hld, ไม่มี);
AttachThreadInput(GetCurrentThreadId, Threadld, True);
MyCursor.Handle := Getcursor();
AttachThreadInput (GetCurrentThreadId, threadld, False);
GetIconInfo (Mycursor.Handle, pIconInfo);
cursorx := DrawPos.x - รอบ (pIconInfo.xHotspot);
เคอร์เซอร์ := DrawPos.y - รอบ (pIconInfo.yHotspot);
Mybmp.Canvas.Draw(cursorx, cursory, MyCursor); {วาดบนเมาส์}
DeleteObject(pIconInfo.hbmColor); {GetIconInfo สร้างวัตถุบิตแมปสองตัวเมื่อใช้ วัตถุทั้งสองนี้จำเป็นต้องเผยแพร่ด้วยตนเอง}
DeleteObject(pIconInfo.hbmMask); {มิฉะนั้น หลังจากเรียกแล้ว มันจะสร้างบิตแมป และการเรียกหลายครั้งจะสร้างหลายรายการจนกว่าทรัพยากรจะหมด}
Mycursor.ReleaseHandle {ปล่อยหน่วยความจำอาร์เรย์}
MyCursor.Free; {ปล่อยตัวชี้เมาส์}
จบ;
จบ;
ขั้นตอน TForm1.FormCreate (ผู้ส่ง: TObject);
เริ่ม
ServerSocket1.พอร์ต := 3000; {พอร์ต}
ServerSocket1.Open; {ซ็อกเก็ตเริ่มฟัง}
จบ;
ขั้นตอน TForm1.FormClose (ผู้ส่ง: TObject; var Action: TCloseAction);
เริ่ม
ถ้า ServerSocket1.Active แล้ว ServerSocket1.Close;
จบ;
ขั้นตอน TForm1.ServerSocket1ClientRead (ผู้ส่ง: TObject;
ซ็อกเก็ต: TCustomWinSocket);
var
S, S1: สตริง;
MyBmp: TBitmap;
Myjpg: TJpegimage;
เริ่ม
S := Socket.ReceiveText;
ถ้า S = 'cap' ดังนั้น {ไคลเอนต์ออกคำสั่งจับภาพหน้าจอ}
เริ่ม
พยายาม
MyStream := TMemorystream.Create;{สร้างสตรีมหน่วยความจำ}
MyBmp := TBitmap.Create;
Myjpg := TJpegimage.Create;
Cjt_GetScreen(MyBmp, True); {True หมายถึงการคว้าภาพเมาส์}
Myjpg.Assign(MyBmp); {แปลงรูปภาพ BMP เป็นรูปแบบ JPG เพื่อให้ง่ายต่อการถ่ายโอนบนอินเทอร์เน็ต}
Myjpg.CompressionQuality := 10; {การตั้งค่าเปอร์เซ็นต์การบีบอัดไฟล์ JPG ยิ่งมีจำนวนมากเท่าไร ภาพก็จะยิ่งชัดเจนมากขึ้นเท่านั้น แต่ข้อมูลก็จะยิ่งมากขึ้น}
Myjpg.SaveToStream(MyStream); {เขียนภาพ JPG เพื่อสตรีม}
Myjpg.free;
MyStream.Position := 0;{หมายเหตุ: ต้องเพิ่มประโยคนี้}
s1 := inttostr (MyStream.size); {ขนาดของสตรีม}
Socket.sendtext(s1); {ส่งขนาดสตรีม}
ในที่สุด
MyBmp.free;
จบ;
จบ;
ถ้า s = 'พร้อม' แล้ว {ลูกค้าพร้อมที่จะรับภาพ}
เริ่ม
MyStream.Position := 0;
Socket.SendStream (MyStream); {ส่งกระแสข้อมูล}
จบ;
จบ;
จบ.
ด้านบนคือเซิร์ฟเวอร์ มาเขียนโปรแกรมไคลเอนต์ด้านล่างกัน สร้างโครงการใหม่และเพิ่มการควบคุมซ็อกเก็ต ClientSocket, การควบคุมการแสดงภาพภาพ, แผง, แก้ไข, สองปุ่มและการควบคุมแถบสถานะ StatusBar1 หมายเหตุ: วาง Edit1 และปุ่มสองปุ่มไว้เหนือ Panel1 คุณลักษณะของ ClientSocket คล้ายกับ ServerSocket แต่มีแอตทริบิวต์ที่อยู่เพิ่มเติม ซึ่งระบุที่อยู่ IP ของเซิร์ฟเวอร์ที่จะเชื่อมต่อ หลังจากกรอกที่อยู่ IP แล้ว คลิก "เชื่อมต่อ" เพื่อสร้างการเชื่อมต่อกับโปรแกรมเซิร์ฟเวอร์ หากสำเร็จ การสื่อสารก็สามารถเริ่มต้นได้ การคลิก "จับภาพหน้าจอ" จะเป็นการส่งตัวอักษรไปยังเซิร์ฟเวอร์ เนื่องจากโปรแกรมใช้หน่วยภาพ JPEG จึงต้องเพิ่ม Jpeg ในการใช้งาน
รหัสทั้งหมดมีดังนี้:
หน่วย Unit2 {ลูกค้า};
อินเตอร์เฟซ
การใช้งาน
Windows, ข้อความ, SysUtils, คลาส, กราฟิก, การควบคุม, แบบฟอร์ม, ไดอะล็อก, StdCtrls, ScktComp, ExtCtrls, Jpeg, ComCtrls;
พิมพ์
TForm1 = คลาส (TForm)
ClientSocket1: TClientSocket;
ภาพที่ 1: TImage;
StatusBar1: TStatusBar;
แผง 1: TPanel;
แก้ไข 1: TEdit;
Button1: T ปุ่ม;
Button2: T ปุ่ม;
ขั้นตอน Button1Click (ผู้ส่ง: TObject);
ขั้นตอน ClientSocket1Connect (ผู้ส่ง: TObject;
ซ็อกเก็ต: TCustomWinSocket);
ขั้นตอน Button2Click (ผู้ส่ง: TObject);
ขั้นตอน ClientSocket1Error (ผู้ส่ง: TObject; ซ็อกเก็ต: TCustomWinSocket;
ErrorEvent: TERrorEvent; var ErrorCode: จำนวนเต็ม);
ขั้นตอน ClientSocket1Read (ผู้ส่ง: TObject; ซ็อกเก็ต: TCustomWinSocket);
ขั้นตอน FormCreate (ผู้ส่ง: TObject);
ขั้นตอน FormClose (ผู้ส่ง: TObject; var Action: TCloseAction);
ขั้นตอน ClientSocket1Disconnect (ผู้ส่ง: TObject;
ซ็อกเก็ต: TCustomWinSocket);
ส่วนตัว
{ประกาศส่วนตัว}
สาธารณะ
{ประกาศสาธารณะ}
จบ;
var
แบบฟอร์ม 1: TForm1;
MySize: Longint;
MyStream: TMemorystream; {วัตถุสตรีมหน่วยความจำ}
การดำเนินการ
{$R *.DFM}
ขั้นตอน TForm1.FormCreate (ผู้ส่ง: TObject);
เริ่ม
{---------------------- ต่อไปนี้คือการตั้งค่าคุณสมบัติลักษณะที่ปรากฏของตัวควบคุมหน้าต่าง------------- }
{หมายเหตุ: วาง Button1, Button2 และ Edit1 ไว้เหนือแผง1}
แก้ไข1.ข้อความ := '127.0.0.1';
Button1.Caption := 'เชื่อมต่อกับโฮสต์';
Button2.Caption := 'จับภาพหน้าจอ';
Button2.Enabled := เท็จ;
Panel1.Align := alTop;
Image1.Align := alClient;
Image1.Stretch := จริง;
StatusBar1.Align:=alBottom;
StatusBar1.SimplePanel := จริง;
-
MyStream := TMemorystream.Create {สร้างวัตถุสตรีมหน่วยความจำ}
ขนาดของฉัน := 0; {การกำหนดค่าเริ่มต้น}
จบ;
ขั้นตอน TForm1.Button1Click (ผู้ส่ง: TObject);
เริ่ม
ถ้าไม่ใช่ ClientSocket1.Active แล้ว
เริ่ม
ClientSocket1.Address := Edit1.Text {ที่อยู่ IP ระยะไกล}
ClientSocket1.Port := 3000; {พอร์ตซ็อกเก็ต}
ClientSocket1.Open; {สร้างการเชื่อมต่อ}
จบ;
จบ;
ขั้นตอน TForm1.Button2Click (ผู้ส่ง: TObject);
เริ่ม
Clientsocket1.Socket.SendText('cap'); {ส่งคำสั่งเพื่อแจ้งเตือนเซิร์ฟเวอร์ให้จับภาพหน้าจอ}
Button2.Enabled := เท็จ;
จบ;
ขั้นตอน TForm1.ClientSocket1Connect (ผู้ส่ง: TObject;
ซ็อกเก็ต: TCustomWinSocket);
เริ่ม
StatusBar1.SimpleText := 'กับโฮสต์' + ClientSocket1.Address + 'สร้างการเชื่อมต่อเรียบร้อยแล้ว!';
button2.enabled: = true;
จบ;
ขั้นตอน TForm1.ClientSocket1Error (ผู้ส่ง: TObject;
ซ็อกเก็ต: TCustomWinSocket; ErrorEvent: TERrorEvent;
varErrorCode: จำนวนเต็ม);
เริ่ม
Errorcode: = 0; {อย่าปรากฏขึ้นในหน้าต่างข้อผิดพลาด}
StatusBar1.SimpleText: = 'ไม่สามารถเชื่อมต่อกับโฮสต์' + ClientSocket1.address + 'การเชื่อมต่อที่สร้างขึ้น!';
จบ;
ขั้นตอน TForm1.ClientSocket1Disconnect (ผู้ส่ง: TObject;
ซ็อกเก็ต: TCustomWinSocket);
เริ่ม
StatusBar1.SimpleText: = 'กับโฮสต์' + clientSocket1.address + 'disconnect!';
button2.enabled: = false;
จบ;
ขั้นตอน TForm1.ClientSocket1Read (ผู้ส่ง: TObject;
ซ็อกเก็ต: TCustomWinSocket);
var
MyBuffer: Array [0..10000] ของ BYTE;
MyRecevicelength: จำนวนเต็ม;
ส: สตริง;
mybmp: tbitmap;
myjpg: tjpegimage;
เริ่ม
StatusBar1.SimpleText: = 'การรับข้อมูล ... ';
ถ้า mysize = 0 ดังนั้น {mysize คือจำนวนไบต์ที่ส่งโดยเซิร์ฟเวอร์
เริ่ม
S: = Socket.ReceiveText;
mysize: = strtoint (s);
clientsocket1.socket.sendtext ('พร้อม');
จบ
อื่น
เริ่มต้น {ต่อไปนี้เป็นข้อมูลภาพที่ได้รับ}
MyRecevicelength: = Socket.Receivelength; {อ่านความยาวแพ็กเก็ต}
StatusBar1.SimpleText: = 'การรับข้อมูลขนาดข้อมูลคือ:' + inttoStr (mysize);
Socket.ReceiveBuf (MyBuffer, MyReceVicelength);
mystream.write (mybuffer, myrecevicelength); {เขียนข้อมูลลงในสตรีม}
หาก mystream.size> = mysize แล้ว {ถ้าความยาวสตรีมมากกว่าจำนวนไบต์ที่จะได้รับการรับเสร็จสิ้น}}
เริ่ม
mystream.position: = 0;
mybmp: = tbitmap.create;
myjpg: = tjpegimage.create;
พยายาม
myjpg.loadfromstream (mystream);
mybmp.assign (myjpg); {แปลง jpg เป็น bmp}
StatusBar1.SimpleText: = 'การแสดงภาพ';
image1.picture.bitmap.assign (mybmp);
ในที่สุด {ต่อไปนี้คืองานล้างข้อมูล}
mybmp.free;
myjpg.free;
button2.enabled: = true;
{socket.sendtext ('cap'); เพิ่มประโยคนี้เพื่อจับภาพหน้าจออย่างต่อเนื่อง}
mystream.clear;
mysize: = 0;
จบ;
จบ;
จบ;
จบ;
ขั้นตอน TForm1.FormClose (ผู้ส่ง: TObject; var Action: TCloseAction);
เริ่ม
mystream.free; {ปล่อยวัตถุสตรีมหน่วยความจำ}
ถ้า clientsocket1.active แล้ว clientsocket1.close; {ปิดการเชื่อมต่อซ็อกเก็ต}
จบ;
จบ.
หลักการของโปรแกรม: เรียกใช้เซิร์ฟเวอร์เพื่อเริ่มการฟังจากนั้นเรียกใช้ไคลเอนต์ป้อนที่อยู่ IP ของเซิร์ฟเวอร์เพื่อสร้างการเชื่อมต่อจากนั้นส่งอักขระเพื่อแจ้งเซิร์ฟเวอร์เพื่อจับภาพหน้าจอ เซิร์ฟเวอร์เรียกใช้ฟังก์ชั่นที่กำหนดเอง CJT_GETSCREEN เพื่อจับภาพหน้าจอและบันทึกเป็น BMP แปลง BMP เป็น JPG เขียน JPG ลงในสตรีมหน่วยความจำแล้วส่งสตรีมไปยังไคลเอนต์ หลังจากได้รับสตรีมไคลเอนต์จะดำเนินการตรงข้ามแปลงสตรีมเป็น JPG จากนั้นไปที่ BMP แล้วแสดง
หมายเหตุ: เนื่องจากข้อ จำกัด ของซ็อกเก็ตข้อมูลที่มีขนาดใหญ่เกินไปไม่สามารถส่งได้ในครั้งเดียว แต่สามารถส่งได้หลายครั้งเท่านั้น ดังนั้นในโปรแกรมหลังจากแปลงการจับภาพหน้าจอลงในสตรีมเซิร์ฟเวอร์จะส่งขนาดของสตรีมก่อนเพื่อแจ้งให้ไคลเอนต์ทราบว่าสตรีมมีขนาดใหญ่แค่ไหน มันจะถูกแปลงและแสดง
โปรแกรมนี้และ OICQ ที่สร้างขึ้นเองก่อนหน้านี้ทั้งคู่ใช้ Object Memory Stream Object TMEMORYSTREAM ในความเป็นจริงวัตถุสตรีมนี้เป็นสิ่งที่ใช้กันมากที่สุดในการเขียนโปรแกรม "คนกลาง" มันดีที่สุด ตัวอย่างเช่นหากคุณบีบอัดหรือคลายการสตรีมคุณจะสร้างวัตถุ tmemorystream ก่อนแล้วคัดลอกข้อมูลอื่น ๆ ลงไปแล้วดำเนินการที่เกี่ยวข้อง เพราะมันทำงานได้โดยตรงในหน่วยความจำประสิทธิภาพจึงสูงมาก บางครั้งคุณจะไม่สังเกตเห็นความล่าช้าใด ๆ
พื้นที่สำหรับการปรับปรุงในโปรแกรม: แน่นอนคุณสามารถเพิ่มหน่วยการบีบอัดเพื่อบีบอัดก่อนส่งแล้วส่ง หมายเหตุ: นอกจากนี้ยังมีเคล็ดลับที่นี่ซึ่งคือการบีบอัด BMP โดยตรงแทนที่จะแปลงเป็น JPG แล้วบีบอัดมัน การทดลองแสดงให้เห็นว่าขนาดของภาพในโปรแกรมข้างต้นมีประมาณ 40-50KB หากคุณต้องการไปเร็วขึ้นคุณสามารถใช้วิธีนี้: ก่อนอื่นจับภาพแรกและส่งจากนั้นเริ่มจากภาพที่สองและส่งภาพในพื้นที่ต่าง ๆ จากภาพก่อนหน้าเท่านั้น มีโปรแกรมต่างประเทศที่เรียกว่าผู้ดูแลระบบระยะไกลที่ใช้วิธีนี้ ข้อมูลที่พวกเขาทดสอบมีดังนี้: 100-500 เฟรมต่อวินาทีในเครือข่ายท้องถิ่นและ 5-10 เฟรมต่อวินาทีบนอินเทอร์เน็ตเมื่อความเร็วเครือข่ายต่ำมาก การพูดคุยกันเหล่านี้ต้องการแสดงให้เห็นถึงความจริงอย่างหนึ่ง: เมื่อคิดถึงปัญหาโดยเฉพาะอย่างยิ่งเมื่อเขียนโปรแกรมโดยเฉพาะอย่างยิ่งโปรแกรมที่ดูซับซ้อนมาก . โปรแกรมตายแล้วพรสวรรค์ยังมีชีวิตอยู่ แน่นอนสิ่งเหล่านี้สามารถพึ่งพาการสะสมของประสบการณ์เท่านั้น แต่การสร้างนิสัยที่ดีตั้งแต่ต้นจะจ่ายเงินปันผลตลอดชีวิตของคุณ!