ทำไมต้องใช้แพ็คเกจ?
คำตอบนั้นง่าย: เนื่องจากพลังของแพ็คเกจ แพ็คเกจเวลาออกแบบช่วยลดความยุ่งยากในการรีลีสและการติดตั้งส่วนประกอบที่กำหนดเอง แพ็คเกจรันไทม์อัดฉีดพลังใหม่ให้กับการเขียนโปรแกรมแบบดั้งเดิม เมื่อคุณคอมไพล์โค้ดที่นำมาใช้ซ้ำได้ในไลบรารีรันไทม์ คุณสามารถแชร์โค้ดดังกล่าวกับหลายแอปพลิเคชันได้ แอปพลิเคชันทั้งหมดสามารถเข้าถึงส่วนประกอบมาตรฐานผ่านแพ็คเกจ และ Delphi เองก็ทำเช่นนั้น เนื่องจากแอปพลิเคชันไม่จำเป็นต้องคัดลอกไลบรารีส่วนประกอบแยกต่างหากในไฟล์ปฏิบัติการ ซึ่งจะช่วยประหยัดทรัพยากรระบบและพื้นที่ดิสก์ได้อย่างมาก นอกจากนี้ แพ็คเกจยังช่วยลดเวลาที่ใช้ในการคอมไพล์ เนื่องจากคุณเพียงแค่ต้องคอมไพล์โค้ดเฉพาะแอปพลิเคชันเท่านั้น
หากสามารถใช้แพ็คเกจแบบไดนามิกได้ เราก็จะได้รับประโยชน์มากขึ้น แพ็คเกจมอบแนวทางแบบโมดูลาร์แบบใหม่ในการพัฒนาแอปพลิเคชัน บางครั้งคุณอาจต้องการสร้างส่วนประกอบเสริมของโมดูลบางโมดูลของแอปพลิเคชัน เช่น ระบบบัญชีที่มาพร้อมกับโมดูล HR เสริม ในบางกรณี คุณเพียงแค่ต้องติดตั้งแอปพลิเคชันพื้นฐานเท่านั้น ในขณะที่ในกรณีอื่นๆ คุณอาจต้องติดตั้งโมดูล HR เพิ่มเติม วิธีการแบบแยกส่วนนี้สามารถนำไปใช้ได้อย่างง่ายดายผ่านเทคโนโลยีแพ็คเกจ ในอดีต สามารถทำได้โดยการโหลด DLL แบบไดนามิกเท่านั้น แต่ด้วยเทคโนโลยีการบรรจุภัณฑ์ของ Delphi คุณสามารถ "รวม" โมดูลแต่ละประเภทของแอปพลิเคชันลงในบันเดิลได้ โดยเฉพาะอย่างยิ่งคลาสอ็อบเจ็กต์ที่สร้างจากแพ็คเกจเป็นของแอปพลิเคชันและสามารถโต้ตอบกับอ็อบเจ็กต์ในแอปพลิเคชันได้
แพ็คเกจรันไทม์และแอปพลิเคชัน
นักพัฒนาหลายคนคิดเพียงว่าแพ็คเกจ Delphi เป็นสถานที่สำหรับวางส่วนประกอบ ซึ่งในความเป็นจริงแล้วแพ็คเกจสามารถ (และควร) ใช้ในการออกแบบแอปพลิเคชันแบบโมดูลาร์
เพื่อสาธิตวิธีใช้แพ็คเกจเพื่อทำให้แอปพลิเคชันของคุณเป็นโมดูล เรามาสร้างตัวอย่างกัน:
1. สร้างโปรแกรม Delphi ใหม่ด้วยสองรูปแบบ: Form1 และ Form2;
2. ลบ Form2 ออกจากรายการแบบฟอร์มที่สร้างขึ้นโดยอัตโนมัติ (โครงการ | ตัวเลือก | แบบฟอร์ม)
3. วางปุ่มบน Form1 และป้อนรหัสต่อไปนี้ในตัวจัดการเหตุการณ์ OnClick ของปุ่ม:
ด้วย TForm2.Create(application) ทำ
เริ่ม
แสดงกิริยา;
ฟรี;
จบ;
4. อย่าลืมเพิ่ม Unit2 ในส่วนคำสั่งการใช้งานของ Unit1;
5. บันทึกและรันโครงการ
เราสร้างแอปพลิเคชันง่ายๆ ที่แสดงแบบฟอร์มด้วยปุ่มที่เมื่อคลิกแล้ว จะสร้างและแสดงแบบฟอร์มอื่น
แต่เราควรทำอย่างไรหากเราต้องการรวม Form2 ในตัวอย่างข้างต้นไว้ใน reusable Module และทำให้มันยังคงทำงานได้ตามปกติ?
คำตอบคือ: เบ้า!
ในการสร้างแพ็คเกจสำหรับ Form2 จำเป็นต้องดำเนินการดังต่อไปนี้:
1. เปิดตัวจัดการโครงการ (ดู | ผู้จัดการโครงการ)
2. คลิกขวาที่กลุ่มโครงการและเลือก "เพิ่มโครงการใหม่...";
3. เลือก “แพ็คเกจ” ในรายการโครงการ “ใหม่”
4. ตอนนี้คุณควรจะเห็นเครื่องมือแก้ไขแพ็คเกจแล้ว
5. เลือกรายการ "มี" และคลิกปุ่ม "เพิ่ม"
6. จากนั้นคลิกปุ่ม "เรียกดู..." และเลือก "Unit2.pas";
7. แพ็คเกจควรมีหน่วย "Unit2.pas"
8. สุดท้ายให้บันทึกและคอมไพล์แพ็คเกจ
ตอนนี้เราได้ทำการแพ็คเก็จเรียบร้อยแล้ว ควรมีไฟล์ชื่อ "package1.bpl" ในไดเร็กทอรี Project/BPL ของคุณ (BPL เป็นตัวย่อของ Borland Package Library และ DCP เป็นตัวย่อของ Delphi CompiledPackage)
แพ็คเกจนี้เสร็จสมบูรณ์ ตอนนี้เราต้องเปิดสวิตช์ตัวเลือกแพ็คเกจ
และคอมไพล์แอปพลิเคชันเดิมอีกครั้ง
1. ดับเบิลคลิก "Project1.exe" ในตัวจัดการโครงการเพื่อเลือกโครงการ
2. คลิกขวาและเลือก "ตัวเลือก..." (คุณสามารถเลือกโครงการ | ตัวเลือก... จากเมนูได้เช่นกัน)
3. เลือกหน้าตัวเลือก “แพ็คเกจ”
4. เลือกช่องทำเครื่องหมาย "สร้างด้วยแพ็คเกจรันไทม์"
5. แก้ไขกล่องแก้ไข "Runtime packages": "Vcl50;Package1" และคลิกปุ่ม "OK"
6. หมายเหตุ: อย่าลบ Unit2 ออกจากแอปพลิเคชัน
7. บันทึกและเรียกใช้แอปพลิเคชัน
แอปพลิเคชันจะทำงานเหมือนเดิม แต่จะเห็นความแตกต่างในขนาดไฟล์
ขณะนี้ Project1.exe มีขนาดเพียง 14K เทียบกับ 293K ก่อนหน้านี้ หากคุณใช้ Resource Browser เพื่อดูเนื้อหาของไฟล์ EXE และ BPL คุณจะเห็นว่า Form2 DFM และโค้ดถูกบันทึกไว้ในแพ็คเกจแล้ว
Delphi ดำเนินการเชื่อมโยงแพ็คเกจแบบคงที่ระหว่างการคอมไพล์ (นี่คือสาเหตุที่คุณไม่สามารถลบ Unit2 ออกจากโครงการ EXE ได้)
ลองนึกถึงสิ่งที่คุณจะได้รับจากสิ่งนี้: คุณสามารถสร้างโมดูลการเข้าถึงข้อมูลในแพ็คเกจได้ และเมื่อคุณเปลี่ยนกฎการเข้าถึงข้อมูล (เช่น การเปลี่ยนจากการเชื่อมต่อ BDE เป็นการเชื่อมต่อ ADO) ให้แก้ไขเล็กน้อยและเผยแพร่แพ็คเกจอีกครั้ง หรือคุณสามารถสร้างฟอร์มในแพ็คเกจเดียวที่แสดงข้อความ "ตัวเลือกนี้ไม่พร้อมใช้งานในเวอร์ชันปัจจุบัน" จากนั้นสร้างฟอร์มที่ใช้งานได้เต็มรูปแบบในแพ็คเกจอื่นที่มีชื่อเดียวกัน ตอนนี้เรามีผลิตภัณฑ์ในเวอร์ชัน "Pro" และ "Enterprise" โดยไม่ต้องใช้ความพยายามใดๆ
การโหลดและการขนถ่ายบรรจุภัณฑ์แบบไดนามิก
ในกรณีส่วนใหญ่ DLL หรือ BPL ที่เชื่อมโยงแบบคงที่ก็เพียงพอแล้ว แต่ถ้าเราไม่ต้องการปล่อย BPL ล่ะ? "ไม่พบไลบรารีลิงก์แบบไดนามิก Package1.bpl ในไดเร็กทอรีที่ระบุ" เป็นข้อความเดียวที่เราจะได้รับก่อนที่แอปพลิเคชันจะยุติลง หรือในแอปพลิเคชันแบบโมดูลาร์ เราสามารถใช้ปลั๊กอินจำนวนเท่าใดก็ได้?
เราจำเป็นต้องเชื่อมต่อกับ BPL แบบไดนามิกขณะรันไทม์
สำหรับ DLL มีวิธีง่ายๆ ซึ่งก็คือการใช้ฟังก์ชัน LoadLibrary:
ฟังก์ชั่น LoadLibrary (lpLibFileName: Pchar): HMODULE; stdcall;
หลังจากโหลด DLL แล้ว เราสามารถใช้ฟังก์ชัน GetProcAddress เพื่อเรียกใช้ฟังก์ชันและวิธีการส่งออกของ DLL ได้:
ฟังก์ชัน GetProcAddress (hModule: HMODULE; lpProcName: LPCSTR): FARPROC;
สุดท้ายนี้ เราใช้ FreeLibrary เพื่อถอนการติดตั้ง DLL:
ฟังก์ชัน FreeLibrary (hLibModule: HMODULE): BOOL; stdcall;
ในตัวอย่างต่อไปนี้ เราโหลดไลบรารี HtmlHelp ของ Microsoft แบบไดนามิก:
ฟังก์ชัน TForm1.ApplicationEvents1Help (คำสั่ง: Word; ข้อมูล: จำนวนเต็ม; var CallHelp: บูลีน): บูลีน;
พิมพ์
TFNHtmlHelpA = ฟังก์ชั่น (hwndCaller: HWND; pszFile: PansiChar; uCommand: UINT; dwData: Dword): HWND;
var
HelpModule: Hmodule;
HtmlHelp: TFNHtmlHelpA;
เริ่ม
ผลลัพธ์ := เท็จ;
HelpModule := LoadLibrary('HHCTRL.OCX');
ถ้า HelpModule <> 0 แล้ว
เริ่ม
@HtmlHelp := GetProcAddress(HelpModule, 'HtmlHelpA');
ถ้า @HtmlHelp <> ไม่มีเลย
ผลลัพธ์ := HtmlHelp(Application.Handle,Pchar(Application.HelpFile), Command,Data) <> 0;
ห้องสมุดอิสระ (โมดูลช่วยเหลือ);
จบ;
CallHelp := เท็จ;
จบ;
กำลังโหลด BPL แบบไดนามิก
เราสามารถใช้วิธีง่ายๆ เดียวกันในการจัดการกับ BPL หรือฉันควรจะพูดโดยพื้นฐานแล้วเป็นวิธีง่ายๆ เดียวกัน
เราสามารถโหลดแพ็คเกจแบบไดนามิกโดยใช้ฟังก์ชัน LoadPackage:
ฟังก์ชั่น LoadPackage (ชื่อ const: string): HMODULE;
จากนั้นใช้ฟังก์ชัน GetClass เพื่อสร้างวัตถุประเภท TPersistentClass:
ฟังก์ชั่น GetClass (const AclassName: string): TPersistentClass;
หลังจากทำทุกอย่างเสร็จแล้ว ให้ใช้ UnLoadPackage(Module:HModule);
มาทำการเปลี่ยนแปลงเล็กๆ น้อยๆ กับโค้ดต้นฉบับ:
1. เลือก "Project1.exe" ในตัวจัดการโครงการ
2. คลิกขวาแล้วเลือก "ตัวเลือก...";
3. เลือกหน้าตัวเลือก “แพ็คเกจ”
4. ลบ "Package1" ออกจากกล่องแก้ไข "Runtime packages" แล้วคลิกปุ่ม OK
5. ในแถบเครื่องมือ Delphi ให้คลิกปุ่ม "ลบไฟล์ออกจากโครงการ"
6. เลือก "Unit2 | Form2" แล้วคลิกตกลง
7. ตอนนี้อยู่ในซอร์สโค้ดของ "Unit1.pas" ให้ลบ Unit2 ออกจากส่วนคำสั่งการใช้งาน
8. ป้อนรหัสเวลา OnClick ของ Button1;
9. เพิ่มตัวแปรสองตัวประเภท HModule และ TPersistentClass:
var
แพคเกจโมดูล: HModule;
คลาส A: TPersistentClass;
10. ใช้ฟังก์ชัน LoadPackage เพื่อโหลดแพ็คเกจ Pacakge1:
PackageModule := LoadPackage('Package1.bpl');
11. ตรวจสอบว่า PackageModule เป็น 0 หรือไม่
12. ใช้ฟังก์ชัน GetClass เพื่อสร้างประเภทถาวร:
AClass := GetClass('TForm2');
13. หากประเภทถาวรนี้ไม่เป็นศูนย์ เราสามารถกลับไปยังประเภทก่อนหน้าได้
สร้างและใช้วัตถุประเภทนี้ในลักษณะเดียวกัน:
ด้วย TComponentClass(AClass).Create(Application) เหมือนที่ TcustomForm ทำ
เริ่ม
แสดงกิริยา;
ฟรี;
จบ;
14. สุดท้าย ใช้กระบวนการ UnloadPackage เพื่อถอนการติดตั้งแพ็คเกจ:
ยกเลิกการโหลดแพ็คเกจ (PackageModule);
15. บันทึกโครงการ
นี่คือรายการตัวจัดการเหตุการณ์ OnClick ทั้งหมด:
ขั้นตอน TForm1.Button1Click (ผู้ส่ง: Tobject);
var
แพคเกจโมดูล: HModule;
คลาส A: TPersistentClass;
เริ่ม
PackageModule := LoadPackage('Package1.bpl');
ถ้า PackageModule <> 0 แล้ว
เริ่ม
AClass := GetClass('TForm2');
ถ้า AClass <> ไม่มีเลย
ด้วย TComponentClass(AClass).Create(Application) เหมือนที่ TcustomForm ทำ
เริ่ม
แสดงกิริยา;
ฟรี;
จบ;
ยกเลิกการโหลดแพ็คเกจ (PackageModule);
จบ;
จบ;
น่าเสียดายที่นั่นไม่ใช่ทั้งหมด
ปัญหาคือฟังก์ชัน GetClass สามารถค้นหาเฉพาะประเภทที่ลงทะเบียนไว้เท่านั้น คลาสแบบฟอร์มและคลาสส่วนประกอบที่โดยปกติจะอ้างอิงในแบบฟอร์มจะถูกลงทะเบียนโดยอัตโนมัติเมื่อมีการโหลดแบบฟอร์ม แต่ในกรณีของเรา ไม่สามารถโหลดแบบฟอร์มก่อนเวลาได้ แล้วเราจะลงทะเบียนประเภทได้ที่ไหน? คำตอบคืออยู่ในกระเป๋า แต่ละหน่วยในบรรจุภัณฑ์จะเริ่มต้นได้เมื่อมีการโหลดบรรจุภัณฑ์และทำความสะอาดเมื่อมีการขนถ่ายบรรจุภัณฑ์
กลับมาที่ตัวอย่างของเรา:
1. ดับเบิลคลิก "Package1.bpl" ในตัวจัดการโครงการ
2. คลิกเครื่องหมาย + ถัดจาก "Unit2" ในส่วน "มี"
3. ดับเบิลคลิก "Unit2.pas" เพื่อเปิดใช้งานโปรแกรมแก้ไขซอร์สโค้ดของหน่วย
4. เพิ่มส่วนการเริ่มต้นที่ส่วนท้ายของไฟล์
5. ใช้ขั้นตอน RegisterClass เพื่อลงทะเบียนประเภทแบบฟอร์ม:
ลงทะเบียนคลาส(TForm2);
6. เพิ่มส่วนการสรุป;
7. ใช้ขั้นตอน UnRegisterClass เพื่อยกเลิกการลงทะเบียนประเภทแบบฟอร์ม:
ยกเลิกการลงทะเบียนClass(TForm2);
8. สุดท้าย ให้บันทึกและคอมไพล์แพ็คเกจ
ตอนนี้เราสามารถรัน "Project1" ได้อย่างปลอดภัยแล้ว และมันจะทำงานได้เหมือนเดิม แต่ตอนนี้คุณสามารถโหลดแพ็คเกจได้ตามที่คุณต้องการ
จบ
โปรดจำไว้ว่า ไม่ว่าคุณจะใช้แพ็คเกจแบบคงที่หรือแบบไดนามิก ให้เปิด Project | Options Package |.
ก่อนที่คุณจะถอนการติดตั้งแพ็คเกจ อย่าลืมทำลายอ็อบเจ็กต์คลาสทั้งหมดในแพ็คเกจ และยกเลิกการลงทะเบียนคลาสที่ลงทะเบียนทั้งหมด กระบวนการต่อไปนี้อาจช่วยคุณได้:
ขั้นตอน DoUnloadPackage (โมดูล: HModule);
var
ฉัน: จำนวนเต็ม;
M: ข้อมูล TMemoryBasicInformation;
เริ่ม
สำหรับ i := Application.ComponentCount - 1 ลงไป 0 ทำ
เริ่ม
VirtualQuery(GetClass(Application.Components[i].ClassName), M, ขนาดของ(M));
ถ้า (Module = 0) หรือ (HMODULE(M.AllocationBase) = Module) แล้ว
Application.Components[i].ฟรี;
จบ;
ยกเลิกการลงทะเบียนโมดูลคลาส (โมดูล);
ยกเลิกการโหลดแพ็คเกจ (โมดูล);
จบ;
ก่อนที่จะโหลดแพ็คเกจ แอปพลิเคชันจำเป็นต้องทราบชื่อของคลาสที่ลงทะเบียนทั้งหมด วิธีหนึ่งในการปรับปรุงสถานการณ์นี้คือการสร้างกลไกการลงทะเบียนที่บอกแอปพลิเคชันถึงชื่อของคลาสทั้งหมดที่ลงทะเบียนโดยแพ็คเกจ
ตัวอย่าง
หลายแพ็คเกจ: แพ็คเกจไม่รองรับการอ้างอิงแบบวงกลม คือหน่วยไม่สามารถอ้างอิงหน่วยที่อ้างอิงหน่วยนั้นได้แล้ว (อิอิ) ทำให้ยากต่อการตั้งค่าบางค่าในรูปแบบการโทรโดยวิธีการที่เรียกว่า
วิธีแก้ไขปัญหานี้คือการสร้างแพ็คเกจเพิ่มเติมบางอย่างที่อ้างอิงโดยทั้งออบเจ็กต์การเรียกและออบเจ็กต์ในแพ็คเกจ ลองนึกภาพว่าเราจะทำให้ Application เป็นเจ้าของทุกรูปแบบได้อย่างไร? แอปพลิเคชันตัวแปรถูกสร้างขึ้นใน Forms.pas และรวมอยู่ในแพ็คเกจ VCL50.bpl คุณอาจสังเกตเห็นว่าแอปพลิเคชันของคุณไม่เพียงแต่ต้องคอมไพล์ VCL50.pas เท่านั้น แต่ยังต้องใช้ VCL50 ในแพ็คเกจของคุณด้วย
ในตัวอย่างที่สามของเรา เราออกแบบแอปพลิเคชันเพื่อแสดงข้อมูลลูกค้าและคำสั่งซื้อของลูกค้า (แบบไดนามิก) ตามความต้องการ
แล้วเราจะเริ่มต้นได้ที่ไหน? เช่นเดียวกับแอปพลิเคชันฐานข้อมูลทั้งหมด
ขั้นตอนเหมือนกัน เราต้องเชื่อมต่อ เราสร้างโมดูลข้อมูลหลักที่มีการเชื่อมต่อ TDataBase จากนั้นเราจะห่อหุ้มโมดูลข้อมูลนี้ไว้ในแพ็คเกจ (cst_main)
ขณะนี้ในแอปพลิเคชัน เราสร้างแบบฟอร์มลูกค้าและอ้างอิง DataModuleMain (เราเชื่อมโยง VCL50 และ cst_main แบบคงที่)
จากนั้นเราสร้างแพ็คเกจใหม่ (cst_ordr) ที่มีแบบฟอร์มการสั่งซื้อของลูกค้าและต้องใช้ cst_main ตอนนี้เราสามารถโหลด cst_ordr ในแอปพลิเคชันแบบไดนามิกได้แล้ว เนื่องจากโมดูลข้อมูลหลักมีอยู่แล้วก่อนที่จะโหลดแพ็คเกจไดนามิก cst_ordr จึงสามารถใช้อินสแตนซ์โมดูลข้อมูลหลักของแอปพลิเคชันได้โดยตรง
รูปภาพด้านบนเป็นแผนภาพการทำงานของแอปพลิเคชันนี้:
แพ็คเกจที่เปลี่ยนได้: กรณีการใช้งานอื่นสำหรับแพ็คเกจคือการสร้างแพ็คเกจที่เปลี่ยนได้ การใช้ฟังก์ชันนี้ไม่จำเป็นต้องมีความสามารถในการโหลดแบบไดนามิกของแพ็คเกจ สมมติว่าเราต้องการเปิดตัวโปรแกรมรุ่นทดลองใช้แบบจำกัดเวลา จะต้องทำอย่างไรจึงจะบรรลุผลสำเร็จ?
ขั้นแรก เราสร้างแบบฟอร์ม "Splash" ซึ่งโดยปกติจะเป็นรูปภาพที่มีคำว่า "Trial" อยู่ และแสดงไว้ในระหว่างการเริ่มต้นแอปพลิเคชัน จากนั้นเราจะสร้างแบบฟอร์ม "เกี่ยวกับ" ที่ให้ข้อมูลบางอย่างเกี่ยวกับแอปพลิเคชัน สุดท้ายนี้ เราสร้างฟังก์ชันที่ทดสอบว่าซอฟต์แวร์ล้าสมัยหรือไม่ เรารวมแบบฟอร์มทั้งสองนี้และฟังก์ชันนี้ไว้ในแพ็คเกจและเผยแพร่พร้อมกับซอฟต์แวร์เวอร์ชันทดลอง
สำหรับเวอร์ชันที่ต้องชำระเงิน เรายังสร้างแบบฟอร์ม "Splash" และแบบฟอร์ม "เกี่ยวกับ" ด้วยชื่อคลาสเดียวกันกับสองแบบฟอร์มก่อนหน้า - และฟังก์ชันทดสอบ (ซึ่งไม่ทำอะไรเลย) และเพิ่มลงในแพ็คเกจที่ห่อหุ้มเหมือนกัน ชื่อ.
อะไรนะ? มันมีประโยชน์ไหมคุณถาม? เราสามารถเผยแพร่ซอฟต์แวร์เวอร์ชันทดลองสู่สาธารณะได้ หากลูกค้าซื้อแอป เราจำเป็นต้องส่งเพียงแพ็คเกจที่ไม่ใช่รุ่นทดลองใช้เท่านั้น สิ่งนี้ทำให้กระบวนการเผยแพร่ซอฟต์แวร์ง่ายขึ้นอย่างมาก เนื่องจากจำเป็นต้องมีการติดตั้งเพียงครั้งเดียวและการอัพเกรดแพ็คเกจการลงทะเบียนหนึ่งครั้ง
แพคเกจนี้เปิดประตูอีกบานสู่การออกแบบโมดูลาร์สำหรับชุมชนการพัฒนา Delphi และ C++ Builder ด้วยแพ็คเกจที่คุณไม่จำเป็นต้องส่งผ่านตัวจัดการหน้าต่างอีกต่อไป ไม่มีฟังก์ชันการเรียกกลับอีกต่อไป ไม่มีเทคโนโลยี DLL อื่น ๆ อีกต่อไป นอกจากนี้ยังช่วยลดระยะเวลาการพัฒนาของการเขียนโปรแกรมแบบโมดูลาร์อีกด้วย สิ่งที่เราต้องทำคือปล่อยให้แพ็คเกจของ Delphi ทำงานแทนเรา