ไลบรารีการเชื่อมโยงแบบไดนามิก
ไลบรารีแบบไดนามิกลิงก์ ( DLL ) คือ การนำแนวคิด ไลบรารีที่ใช้ร่วมกันมาใช้ของMicrosoftในระบบปฏิบัติการMicrosoft WindowsและOS/2ไลบรารีเหล่านี้มักจะมีนามสกุลไฟล์เป็น .dll.js (สำหรับไลบรารีที่มี ตัวควบคุม ActiveX ) หรือ .exe (สำหรับไดรเวอร์ระบบ แบบเก่า ) รูปแบบไฟล์ของ DLL จะเหมือนกับ ไฟล์ EXE ของ Windows กล่าวคือPortable Executable (PE) สำหรับ Windows 32 บิตและ64 บิตและNew Executable (NE) สำหรับ Windows 16 บิต เช่นเดียวกับไฟล์ EXE ไฟล์ DLL สามารถมีโค้ดข้อมูลและทรัพยากรในรูปแบบใดก็ได้DLLOCXDRV
ไฟล์ข้อมูลที่มีรูปแบบไฟล์เหมือนกับ DLL แต่มีนามสกุลไฟล์ต่างกัน และอาจมีเฉพาะส่วนทรัพยากรเท่านั้น สามารถเรียกว่าDLL ทรัพยากรได้ ตัวอย่างของ DLL ดังกล่าว ได้แก่ไลบรารีไอคอนซึ่งบางครั้งมีนามสกุลICLและ ไฟล์ ฟอนต์ซึ่งมีนามสกุลFONและFOT[ 1 ]
พื้นหลัง
ระบบปฏิบัติการMicrosoft Windowsเวอร์ชันแรกๆ นั้นโปรแกรมต่างๆ ทำงานร่วมกันในพื้นที่หน่วยความจำ เดียว แต่ละโปรแกรมถูกออกแบบมาให้ทำงานร่วมกันโดยการแบ่งปันทรัพยากร CPU ให้กับโปรแกรมอื่นๆ เพื่อให้ส่วนติดต่อผู้ใช้แบบกราฟิก (GUI) สามารถทำงานหลายอย่างพร้อมกันและตอบสนองได้อย่างรวดเร็วที่สุด การทำงานระดับระบบปฏิบัติการทั้งหมดนั้นมาจากระบบปฏิบัติการพื้นฐาน: MS- DOS ส่วนบริการระดับสูงทั้งหมดนั้นมาจากไลบรารีของ Windows หรือ "Dynamic Link Library" API สำหรับการวาดภาพ หรือGraphics Device Interface (GDI) นั้นถูกนำไปใช้ใน DLL ที่เรียกว่า ` GDI.js` ซึ่งเป็น GDI.EXEส่วนติดต่อผู้ใช้ในUSER.EXE`GDI.js` เลเยอร์เพิ่มเติมเหล่านี้บน DOS ต้องใช้ร่วมกันในโปรแกรม Windows ที่กำลังทำงานอยู่ทั้งหมด ไม่ใช่แค่เพื่อให้ Windows สามารถทำงานในเครื่องที่มี RAM น้อยกว่า 1 เมกะไบต์เท่านั้น แต่ยังเพื่อให้โปรแกรมต่างๆ สามารถทำงานร่วมกันได้ด้วย โค้ดใน GDI จำเป็นต้องแปลงคำสั่งการวาดภาพไปเป็นการทำงานบนอุปกรณ์เฉพาะ บนจอแสดงผล โค้ดจะต้องจัดการพิกเซลในเฟรมบัฟเฟอร์ เมื่อวาดภาพไปยังเครื่องพิมพ์ การเรียกใช้ API จะต้องถูกแปลงเป็นคำขอไปยังเครื่องพิมพ์ แม้ว่าจะเป็นไปได้ที่จะให้การสนับสนุนแบบฮาร์ดโค้ดสำหรับอุปกรณ์จำนวนจำกัด (เช่น จอแสดง ผล Color Graphics Adapter , ภาษาคำสั่งเครื่องพิมพ์ HP LaserJet ) แต่ Microsoft เลือกใช้วิธีการที่แตกต่างออกไป GDI จะทำงานโดยการโหลดโค้ดส่วนต่างๆ ที่เรียกว่า " ไดรเวอร์อุปกรณ์ " เพื่อทำงานร่วมกับอุปกรณ์แสดงผลต่างๆ
แนวคิดทางสถาปัตยกรรมเดียวกันที่ทำให้ GDI สามารถโหลดไดรเวอร์อุปกรณ์ต่างๆ ได้นั้น ยังทำให้เชลล์ของ Windowsสามารถโหลดโปรแกรมต่างๆ ของ Windows ได้ และทำให้โปรแกรมเหล่านั้นสามารถเรียกใช้ API จากไลบรารี USER และ GDI ที่ใช้ร่วมกันได้ แนวคิดนั้นคือ "การเชื่อมโยงแบบไดนามิก" (dynamic linking)
ในไลบรารีแบบคงที่ ที่ไม่ใช้ร่วมกันแบบดั้งเดิม ส่วนของโค้ดจะถูกเพิ่มเข้าไปในโปรแกรมที่เรียกใช้เมื่อมีการสร้างไฟล์ปฏิบัติการในขั้นตอน "การเชื่อมโยง" หากโปรแกรมสองโปรแกรมเรียกใช้รูทีนเดียวกัน รูทีนนั้นจะถูกรวมอยู่ในทั้งสองโปรแกรมในระหว่างขั้นตอนการเชื่อมโยงของทั้งสองโปรแกรม ส่วนการเชื่อมโยงแบบไดนามิกนั้น โค้ดที่ใช้ร่วมกันจะถูกจัดวางไว้ในไฟล์เดียวที่แยกต่างหาก โปรแกรมที่เรียกใช้ไฟล์นี้จะเชื่อมต่อกับไฟล์นั้นในระหว่างการทำงาน โดยระบบปฏิบัติการ (หรือในกรณีของ Windows เวอร์ชันแรกๆ คือส่วนขยายของระบบปฏิบัติการ) จะเป็นผู้ทำการเชื่อมโยง
สำหรับ Windows รุ่นแรกๆ (1.0 ถึง 3.11) ไฟล์ DLL เป็นรากฐานของ GUI ทั้งหมด ดังนั้น ไดรเวอร์แสดงผลจึงเป็นเพียงไฟล์ DLL ที่มีนามสกุล .DRV ซึ่งให้การใช้งาน API การวาดภาพแบบกำหนดเองผ่าน อินเทอร์เฟ ซไดรเวอร์อุปกรณ์ แบบรวม (DDI) และ API การวาดภาพ (GDI) และ GUI (USER) ก็เป็นเพียงการเรียกใช้ฟังก์ชันที่ส่งออกโดย GDI และ USER ซึ่งเป็นไฟล์ DLL ของระบบที่มีนามสกุล .EXE
แนวคิดในการสร้างระบบปฏิบัติการจากชุดไลบรารีที่โหลดแบบไดนามิกนี้เป็นแนวคิดหลักของ Windows ที่ยังคงมีอยู่จนถึงปี 2015ไฟล์ DLL ให้ประโยชน์มาตรฐานของไลบรารีที่ใช้ร่วมกันเช่นความเป็นโมดูลาร์ความเป็นโมดูลาร์ช่วยให้สามารถเปลี่ยนแปลงโค้ดและข้อมูลในไฟล์ DLL ที่สมบูรณ์ในตัวเอง ซึ่งใช้ร่วมกันโดยหลายแอปพลิเคชัน โดยไม่ต้องเปลี่ยนแปลงตัวแอปพลิเคชันเหล่านั้นเลย
อีกหนึ่งประโยชน์ของการออกแบบแบบโมดูลาร์คือการใช้อินเทอร์เฟซทั่วไปสำหรับปลั๊กอิน สามารถพัฒนาอินเทอร์เฟซเดียวที่ช่วยให้โมดูลเก่าและใหม่สามารถผสานรวมเข้ากับแอปพลิเคชันที่มีอยู่แล้วได้อย่างราบรื่นในระหว่างการทำงาน โดยไม่ต้องแก้ไขแอปพลิเคชันนั้นเลย แนวคิดเรื่องความสามารถในการขยายแบบไดนามิกนี้ถูกนำไปใช้ในระดับสูงสุดด้วยComponent Object Modelซึ่งเป็นรากฐานของActiveX
ใน Windows 1.x, 2.x และ 3.x แอปพลิเคชัน Windows ทั้งหมดใช้พื้นที่แอดเดรสและหน่วยความจำร่วมกัน ไฟล์ DLL จะถูกโหลดลงในพื้นที่แอดเดรสนี้เพียงครั้งเดียวเท่านั้น หลังจากนั้น โปรแกรมทั้งหมดที่ใช้ไลบรารีจะเข้าถึงไลบรารีนั้น ข้อมูลของไลบรารีจะถูกใช้ร่วมกันในทุกโปรแกรม ซึ่งอาจใช้เป็นการสื่อสารระหว่างกระบวนการ แบบทางอ้อม หรืออาจทำให้โปรแกรมต่างๆ เสียหายได้โดยไม่ตั้งใจ เมื่อมีการนำ ไลบรารี 32 บิต มาใช้ ในWindows 95ทุกกระบวนการจะทำงานในพื้นที่แอดเดรสของตนเอง แม้ว่าโค้ดของ DLL จะถูกใช้ร่วมกัน แต่ข้อมูลจะเป็นส่วนตัว ยกเว้นในกรณีที่ไลบรารีร้องขอข้อมูลที่ใช้ร่วมกันอย่างชัดเจน อย่างไรก็ตามWindows 95 , Windows 98และWindows Me ส่วนใหญ่ สร้างขึ้นจากไลบรารี 16 บิต ซึ่งจำกัดประสิทธิภาพของ ไมโครโปรเซสเซอร์ Pentium Proเมื่อเริ่มใช้งาน และในที่สุดก็จำกัดความเสถียรและความสามารถในการขยายขนาดของ Windows เวอร์ชันที่ใช้ DOS
แม้ว่า DLL จะเป็นแกนหลักของสถาปัตยกรรม Windows แต่ก็มีข้อเสียหลายประการ ซึ่งโดยรวมเรียกว่า " DLL hell " [ 2 ]ณ ปี 2015ไมโครซอฟต์ส่งเสริม . NET Frameworkเป็นหนึ่งในวิธีแก้ปัญหา DLL hell แม้ว่าปัจจุบันพวกเขาจะส่งเสริมโซลูชันที่ใช้การจำลองเสมือน เช่นMicrosoft Virtual PCและMicrosoft Application Virtualizationมากกว่า เนื่องจากโซลูชันเหล่านี้ให้การแยกส่วนระหว่างแอปพลิเคชันที่ดีกว่า อีกทางเลือกหนึ่งในการลดปัญหา DLL hell คือการใช้ภาษาแอสเซมบลีแบบคู่ขนาน (side-by-side assembly )
คุณสมบัติ
เนื่องจากไฟล์ DLL นั้นโดยพื้นฐานแล้วเหมือนกับไฟล์ EXE การเลือกที่จะสร้างไฟล์ใดไฟล์หนึ่งในระหว่างกระบวนการเชื่อมโยงจึงขึ้นอยู่กับความชัดเจน เพราะสามารถส่งออกฟังก์ชันและข้อมูลจากทั้งสองไฟล์ได้
ไม่สามารถเรียกใช้งาน DLL ได้โดยตรง เนื่องจากต้องมีไฟล์ EXE เพื่อให้ระบบปฏิบัติการโหลดผ่านจุดเริ่มต้น (entry point ) ดังนั้นจึงมีโปรแกรมยูทิลิตี้ เช่น RUNDLL.EXE หรือ RUNDLL32.EXE ซึ่งทำหน้าที่เป็นจุดเริ่มต้นและสร้างโครงสร้างพื้นฐานขั้นต่ำสำหรับ DLL ที่มีฟังก์ชันการทำงานเพียงพอที่จะเรียกใช้งานได้โดยไม่ต้องมีการสนับสนุนมากนัก
ไฟล์ DLL เป็นกลไกสำหรับการใช้โค้ดและข้อมูลร่วมกัน ทำให้ผู้พัฒนาโค้ด/ข้อมูลที่ใช้ร่วมกันสามารถอัปเกรดฟังก์ชันการทำงานได้โดยไม่ต้องทำการเชื่อมโยงหรือคอมไพล์แอปพลิเคชันใหม่ จากมุมมองของการพัฒนาแอปพลิเคชัน Windows และ OS/2 สามารถมองได้ว่าเป็นชุดของไฟล์ DLL ที่ได้รับการอัปเกรด ทำให้แอปพลิเคชันสำหรับระบบปฏิบัติการเวอร์ชันหนึ่งสามารถทำงานได้ในเวอร์ชันที่ใหม่กว่า โดยมีเงื่อนไขว่าผู้จำหน่ายระบบปฏิบัติการได้ตรวจสอบให้แน่ใจแล้วว่าอินเทอร์เฟซและฟังก์ชันการทำงานเข้ากันได้
ไฟล์ DLL จะทำงานในพื้นที่หน่วยความจำของกระบวนการที่เรียกใช้ และมีสิทธิ์การเข้าถึงเหมือนกัน ซึ่งหมายความว่าจะมีค่าใช้จ่ายในการใช้งานน้อย แต่ก็หมายความว่าโปรแกรมที่เรียกใช้จะไม่มีการป้องกันใดๆ หากไฟล์ DLL มีข้อผิดพลาด
การจัดการหน่วยความจำ
ในWindows APIไฟล์ DLL จะถูกจัดระเบียบเป็นส่วนต่างๆแต่ละส่วนจะมีคุณลักษณะเฉพาะของตนเอง เช่น สามารถเขียนได้หรืออ่านได้อย่างเดียว สามารถเรียกใช้งานได้ (สำหรับโค้ด) หรือไม่สามารถเรียกใช้งานได้ (สำหรับข้อมูล) เป็นต้น
โดยปกติแล้ว โค้ดใน DLL จะถูกใช้ร่วมกันระหว่างกระบวนการทั้งหมดที่ใช้ DLL นั้น กล่าวคือ โค้ดจะใช้พื้นที่ในหน่วยความจำทางกายภาพเพียงที่เดียว และไม่ใช้พื้นที่ในไฟล์เพจ Windows ไม่ได้ใช้โค้ดที่ไม่ขึ้นกับตำแหน่งสำหรับ DLL แต่โค้ดจะถูกย้ายตำแหน่งเมื่อถูกโหลด โดยกำหนดแอดเดรสสำหรับจุดเริ่มต้นทั้งหมดไว้ที่ตำแหน่งว่างในพื้นที่หน่วยความจำของกระบวนการแรกที่โหลด DLL นั้น ใน Windows เวอร์ชันเก่า ซึ่งกระบวนการที่ทำงานอยู่ทั้งหมดใช้พื้นที่แอดเดรสร่วมกันเพียงที่เดียว โค้ดของ DLL เพียงชุดเดียวก็เพียงพอสำหรับทุกกระบวนการเสมอ อย่างไรก็ตาม ใน Windows เวอร์ชันใหม่กว่า ซึ่งใช้พื้นที่แอดเดรสแยกต่างหากสำหรับแต่ละโปรแกรม การใช้โค้ด DLL ที่ย้ายตำแหน่งแล้วชุดเดียวกันในหลายโปรแกรมนั้นเป็นไปได้ก็ต่อเมื่อแต่ละโปรแกรมมีแอดเดรสเสมือนที่ว่างอยู่เพื่อรองรับโค้ดของ DLL นั้น หากบางโปรแกรม (หรือการรวมกันของ DLL ที่โหลดแล้ว) ไม่มีแอดเดรสเหล่านั้นว่างอยู่ จะต้องสร้างโค้ด DLL สำเนาทางกายภาพเพิ่มเติม โดยใช้ชุดจุดเริ่มต้นที่ย้ายตำแหน่งแล้วชุดอื่น หากจำเป็นต้องเรียกคืนหน่วยความจำทางกายภาพที่ส่วนของโค้ดใช้งานอยู่ เนื้อหาของส่วนนั้นจะถูกทิ้งไป และจะถูกโหลดกลับมาใหม่โดยตรงจากไฟล์ DLL ตามความจำเป็น
ตรงกันข้ามกับส่วนของโค้ด ส่วนข้อมูลของ DLL มักจะเป็นส่วนตัว กล่าวคือ แต่ละกระบวนการที่ใช้ DLL จะมีสำเนาข้อมูลทั้งหมดของ DLL เป็นของตัวเอง ในบางกรณี ส่วนข้อมูลอาจใช้ร่วมกันได้ ซึ่งจะช่วยให้การสื่อสารระหว่างกระบวนการ เกิดขึ้น ได้ผ่านพื้นที่หน่วยความจำที่ใช้ร่วมกันนี้ อย่างไรก็ตาม เนื่องจากข้อจำกัดของผู้ใช้ไม่ได้มีผลบังคับใช้กับการใช้หน่วยความจำ DLL ที่ใช้ร่วมกัน จึงทำให้เกิดช่องโหว่ด้านความปลอดภัยกล่าวคือ กระบวนการหนึ่งสามารถทำให้ข้อมูลที่ใช้ร่วมกันเสียหาย ซึ่งอาจทำให้กระบวนการอื่นๆ ที่ใช้ข้อมูลร่วมกันทำงานผิดปกติได้ ตัวอย่างเช่น กระบวนการที่ทำงานภายใต้บัญชีผู้ใช้ทั่วไปสามารถทำให้กระบวนการอื่นที่ทำงานภายใต้บัญชีผู้ใช้ที่มีสิทธิ์พิเศษเสียหายได้ นี่เป็นเหตุผลสำคัญที่ควรหลีกเลี่ยงการใช้ส่วนที่ใช้ร่วมกันใน DLL
หากไฟล์ DLL ถูกบีบอัดด้วยโปรแกรมบีบอัดไฟล์ บางประเภท (เช่นUPX ) ส่วนของโค้ดทั้งหมดจะถูกทำเครื่องหมายว่าเป็นส่วนที่อ่านและเขียนได้ และจะไม่ถูกแชร์ ส่วนของโค้ดที่อ่านและเขียนได้นั้น เช่นเดียวกับส่วนข้อมูลส่วนตัว จะเป็นส่วนตัวสำหรับแต่ละกระบวนการ ดังนั้น ไฟล์ DLL ที่มีส่วนข้อมูลที่ใช้ร่วมกันไม่ควรถูกบีบอัดหากต้องการให้โปรแกรมหลายโปรแกรมใช้งานพร้อมกัน เนื่องจากแต่ละอินสแตนซ์ของโปรแกรมจะต้องมีสำเนาของไฟล์ DLL เป็นของตัวเอง ซึ่งจะทำให้สิ้นเปลืองหน่วยความจำมากขึ้น
นำเข้าไลบรารี
เช่นเดียวกับไลบรารีแบบสแตติก ไลบรารีสำหรับนำเข้า DLL จะระบุด้วย.libนามสกุลไฟล์ ตัวอย่างเช่นkernel32.dllซึ่งเป็นไลบรารีแบบไดนามิกหลักสำหรับฟังก์ชันพื้นฐานของ Windows เช่น การสร้างไฟล์และการจัดการหน่วยความจำ จะถูกเชื่อมโยงผ่านkernel32.lib.dll วิธีปกติในการแยกแยะไลบรารีสำหรับนำเข้าจากไลบรารีแบบสแตติกที่ถูกต้องคือการดูขนาด: ไลบรารีสำหรับนำเข้าจะมีขนาดเล็กกว่ามาก เนื่องจากมีเพียงสัญลักษณ์ที่อ้างอิงถึง DLL จริง ซึ่งจะถูกประมวลผลในระหว่างการเชื่อมโยง อย่างไรก็ตาม ทั้งสองประเภทเป็นไฟล์รูปแบบ Unix .ar เหมือนกัน
การเชื่อมโยงไปยังไลบรารีแบบไดนามิกมักจะจัดการโดยการเชื่อมโยงไปยังไลบรารีนำเข้าเมื่อสร้างหรือเชื่อมโยงเพื่อสร้างไฟล์ปฏิบัติการ ไฟล์ปฏิบัติการที่สร้างขึ้นจะมีตารางที่อยู่การนำเข้า (IAT) ซึ่งใช้อ้างอิงการเรียกใช้ฟังก์ชัน DLL ทั้งหมด (แต่ละฟังก์ชัน DLL ที่อ้างอิงจะมีรายการของตัวเองใน IAT) ในระหว่างการทำงาน IAT จะถูกเติมด้วยที่อยู่ที่เหมาะสมซึ่งชี้ไปยังฟังก์ชันใน DLL ที่โหลดแยกต่างหากโดยตรง[ 3 ]
ใน Cygwin/MSYS และ MinGW ไลบรารีนำเข้ามักจะได้รับคำต่อท้าย โดย.dll.aรวมทั้งคำต่อท้าย DLL ของ Windows และคำต่อท้าย ar ของ Unix รูปแบบไฟล์คล้ายกัน แต่สัญลักษณ์ที่ใช้ในการทำเครื่องหมายการนำเข้าจะแตกต่างกัน ( _head_foo_dllเทียบกับ__IMPORT_DESCRIPTOR_foo) [ 4 ]แม้ว่า ชุดเครื่องมือ GNU Binutilsจะสามารถสร้างไลบรารีนำเข้าและเชื่อมโยงไปยังไลบรารีเหล่านั้นได้ แต่การเชื่อมโยงไปยัง DLL โดยตรงนั้นเร็วกว่า[ 5 ]เครื่องมือทดลองใน MinGW ที่เรียกว่า genlib สามารถใช้เพื่อสร้างไลบรารีนำเข้าด้วยสัญลักษณ์แบบ MSVC ได้
การแก้ไขสัญลักษณ์และการผูกมัด
แต่ละฟังก์ชันที่ส่งออกโดย DLL จะถูกระบุด้วยลำดับตัวเลขและอาจมีชื่อด้วย ในทำนองเดียวกัน ฟังก์ชันสามารถนำเข้าจาก DLL ได้ทั้งโดยลำดับหรือโดยชื่อ ลำดับแสดงถึงตำแหน่งของตัวชี้แอดเดรสของฟังก์ชันในตารางแอดเดรสการส่งออกของ DLL โดยทั่วไปแล้ว ฟังก์ชันภายในจะถูกส่งออกโดยใช้ลำดับเท่านั้น สำหรับฟังก์ชัน Windows API ส่วนใหญ่ เฉพาะชื่อเท่านั้นที่จะถูกเก็บรักษาไว้ใน Windows เวอร์ชันต่างๆ ลำดับอาจเปลี่ยนแปลงได้ ดังนั้นจึงไม่สามารถนำเข้าฟังก์ชัน Windows API โดยใช้ลำดับได้อย่างน่าเชื่อถือ
การนำเข้าฟังก์ชันโดยใช้ลำดับจะให้ประสิทธิภาพดีกว่าการนำเข้าโดยใช้ชื่อเพียงเล็กน้อยเท่านั้น เนื่องจากตารางการส่งออกของ DLL จะเรียงลำดับตามชื่อ ดังนั้นจึง สามารถใช้ การค้นหาแบบไบนารีเพื่อค้นหาฟังก์ชันได้ จากนั้นจะใช้ดัชนีของชื่อที่พบเพื่อค้นหาลำดับในตารางลำดับการส่งออก ใน Windows 16 บิต ตารางชื่อไม่ได้เรียงลำดับ ดังนั้นค่าใช้จ่ายในการค้นหาชื่อจึงสังเกตเห็นได้ชัดเจนมากขึ้น
นอกจากนี้ยังสามารถผูกไฟล์ปฏิบัติการเข้ากับ DLL เวอร์ชันเฉพาะได้ กล่าวคือ สามารถระบุที่อยู่ของฟังก์ชันที่นำเข้าได้ในระหว่างการคอมไพล์ สำหรับการผูกการนำเข้า ตัวเชื่อมโยงจะบันทึกเวลาและค่าตรวจสอบของ DLL ที่นำเข้าไว้ ในระหว่างการทำงาน Windows จะตรวจสอบว่ามีการใช้ไลบรารีเวอร์ชันเดียวกันหรือไม่ ถ้าใช่ Windows จะข้ามการประมวลผลการนำเข้าไป แต่ถ้าไลบรารีที่ใช้แตกต่างจากที่ผูกไว้ Windows จะประมวลผลการนำเข้าตามปกติ
ไฟล์ปฏิบัติการที่ผูกไว้จะโหลดเร็วขึ้นเล็กน้อยหากทำงานในสภาพแวดล้อมเดียวกับที่คอมไพล์ไว้ และจะใช้เวลาเท่ากันหากทำงานในสภาพแวดล้อมที่แตกต่างกัน ดังนั้นจึงไม่มีข้อเสียใดๆ ในการผูกการนำเข้า ตัวอย่างเช่น แอปพลิเคชัน Windows มาตรฐานทั้งหมดจะผูกกับ DLL ของระบบใน Windows เวอร์ชันนั้นๆ โอกาสที่ดีในการผูกการนำเข้าของแอปพลิเคชันกับสภาพแวดล้อมเป้าหมายคือระหว่างการติดตั้งแอปพลิเคชัน วิธีนี้จะทำให้ไลบรารี "ผูกอยู่" จนกว่าจะมีการอัปเดตระบบปฏิบัติการครั้งถัดไป อย่างไรก็ตาม วิธีนี้จะเปลี่ยนค่าตรวจสอบความถูกต้องของไฟล์ปฏิบัติการ ดังนั้นจึงไม่สามารถใช้ได้กับโปรแกรมที่ลงนามแล้ว หรือโปรแกรมที่จัดการโดยเครื่องมือจัดการการกำหนดค่าที่ใช้ค่าตรวจสอบความถูกต้อง (เช่น ค่าตรวจสอบความถูกต้อง MD5 ) ในการจัดการเวอร์ชันไฟล์ เนื่องจาก Windows เวอร์ชันใหม่ๆ ได้เปลี่ยนไปใช้ที่อยู่ที่ไม่คงที่สำหรับทุกไลบรารีที่โหลด (ด้วยเหตุผลด้านความปลอดภัย) โอกาสและคุณค่าของการผูกไฟล์ปฏิบัติการจึงลดลง
การเชื่อมโยงรันไทม์แบบชัดเจน
ไฟล์ DLL อาจถูกโหลดอย่างชัดเจนในระหว่างการทำงาน ซึ่งกระบวนการนี้Microsoft เรียกว่า การเชื่อมโยงแบบไดนามิกในระหว่างการทำงาน (run-time dynamic linkingLoadLibrary ) โดยใช้ฟังก์ชัน API (หรือLoadLibraryEx) GetProcAddressฟังก์ชัน API นี้ใช้ในการค้นหาสัญลักษณ์ที่ส่งออกตามชื่อ และFreeLibrary– ใช้ในการยกเลิกการโหลด DLL ฟังก์ชันเหล่านี้คล้ายคลึงกับdlopen, dlsym, และdlcloseในAPI มาตรฐานPOSIX
ขั้นตอนการเชื่อมโยงรันไทม์แบบชัดเจนนั้นเหมือนกันในทุกภาษาที่รองรับตัวชี้ไปยังฟังก์ชันเนื่องจากขึ้นอยู่กับAPI ของ Windowsไม่ใช่โครงสร้างของภาษา
การโหลดล่าช้า
โดยปกติแล้ว แอปพลิเคชันที่เชื่อมโยงกับไลบรารีนำเข้าของ DLL จะไม่สามารถเริ่มต้นได้หากไม่พบ DLL เนื่องจาก Windows จะไม่เรียกใช้แอปพลิเคชันเว้นแต่จะพบ DLL ทั้งหมดที่แอปพลิเคชันอาจต้องการ อย่างไรก็ตาม แอปพลิเคชันอาจเชื่อมโยงกับไลบรารีนำเข้าเพื่ออนุญาตให้โหลดไลบรารีแบบไดนามิกแบบล่าช้า[ 6 ] ในกรณีนี้ ระบบปฏิบัติการจะไม่พยายามค้นหาหรือโหลด DLL เมื่อแอปพลิเคชันเริ่มต้น แต่ตัวเชื่อมโยงจะรวม stub ไว้ในแอปพลิเคชัน ซึ่งจะพยายามค้นหาและโหลด DLL LoadLibraryเมื่อGetProcAddressมีการเรียกใช้ฟังก์ชันใดฟังก์ชันหนึ่ง หากไม่พบหรือโหลด DLL ไม่ได้ หรือฟังก์ชันที่เรียกใช้ไม่มีอยู่ แอปพลิเคชันจะสร้างข้อยกเว้นซึ่งอาจถูกดักจับและจัดการอย่างเหมาะสม หากแอปพลิเคชันไม่จัดการข้อยกเว้น ระบบปฏิบัติการจะดักจับข้อยกเว้นนั้นและยุติโปรแกรมพร้อมข้อความแสดงข้อผิดพลาด
กลไกการโหลดแบบหน่วงเวลายังมีกลไก การแจ้งเตือน ซึ่งช่วยให้แอปพลิเคชันสามารถดำเนินการประมวลผลเพิ่มเติมหรือจัดการข้อผิดพลาดเมื่อโหลด DLL และ/หรือเมื่อมีการเรียกใช้ฟังก์ชันใดๆ ของ DLL
ข้อควรพิจารณาเกี่ยวกับคอมไพเลอร์และภาษา
เดลฟี
ในไฟล์ต้นฉบับlibraryจะใช้คีย์เวิร์ดแทนprogramในส่วนท้ายของไฟล์ จะมีการระบุฟังก์ชันที่จะส่งออกไว้ในexportsส่วนของเงื่อนไข
Delphiไม่จำเป็นต้องใช้LIBไฟล์เพื่อนำเข้าฟังก์ชันจาก DLL; ในการเชื่อมโยงกับ DLL externalจะใช้คำหลักในคำประกาศฟังก์ชันเพื่อระบุชื่อ DLL ตามด้วยnameชื่อสัญลักษณ์ (ถ้าแตกต่างกัน) หรือindexเพื่อระบุหมายเลขดัชนี
ไมโครซอฟต์ วิชวลเบส
ในVisual Basic (VB) รองรับเฉพาะการเชื่อมโยงขณะรันไทม์เท่านั้น แต่เพิ่มเติมจากการใช้ฟังก์ชัน API แล้วLoadLibraryยังอนุญาตให้ประกาศ ฟังก์ชันที่นำเข้าได้อีกด้วยGetProcAddress
เมื่อนำเข้าฟังก์ชันจาก DLL ผ่านการประกาศ VB จะสร้างข้อผิดพลาดขณะรันไทม์หากDLLไม่พบไฟล์ นักพัฒนาสามารถดักจับข้อผิดพลาดและจัดการได้อย่างเหมาะสม
เมื่อสร้าง DLL ใน VB IDE จะอนุญาตให้สร้างเฉพาะ DLL ของ ActiveX เท่านั้น อย่างไรก็ตาม มีการสร้างวิธีการ[ 7 ]เพื่ออนุญาตให้ผู้ใช้บอกลิงเกอร์อย่างชัดเจนให้รวมไฟล์ .DEF ซึ่งกำหนดตำแหน่งลำดับและชื่อของแต่ละฟังก์ชันที่ส่งออก วิธีนี้ช่วยให้ผู้ใช้สามารถสร้าง DLL มาตรฐานของ Windows โดยใช้ Visual Basic (เวอร์ชัน 6 หรือต่ำกว่า) ซึ่งสามารถอ้างอิงได้ผ่านคำสั่ง "Declare"
ซี และ ซี++
Microsoft Visual C++ (MSVC) มีส่วนขยายหลายอย่างสำหรับC++ มาตรฐาน ที่อนุญาตให้ระบุฟังก์ชันเป็นนำเข้าหรือส่งออกโดยตรงในโค้ด C++ ส่วนขยายเหล่านี้ได้รับการนำไปใช้โดย คอมไพเลอร์ Cและ C++ อื่นๆ ของ Windows รวมถึงGCC เวอร์ชัน Windows ส่วนขยายเหล่านี้ใช้แอตทริบิวต์__declspecก่อนการประกาศฟังก์ชัน โปรดทราบว่าเมื่อเข้าถึงฟังก์ชัน C จาก C++ ฟังก์ชันเหล่านั้นจะต้องถูกประกาศในextern "C"โค้ด C++ ด้วย เพื่อแจ้งให้คอมไพเลอร์ทราบว่าควรใช้การเชื่อมโยง C [ 8 ]
นอกจากการระบุฟังก์ชันที่นำเข้าหรือส่งออกโดยใช้__declspecแอตทริบิวต์แล้ว ฟังก์ชันเหล่านั้นอาจแสดงอยู่ในส่วน IMPORT หรือ EXPORTS ของDEFไฟล์ที่ใช้โดยโปรเจกต์DEFไฟล์ดังกล่าวจะถูกประมวลผลโดยลิงเกอร์ ไม่ใช่คอมไพเลอร์ ดังนั้นจึงไม่จำกัดเฉพาะภาษา C++
การคอมไพล์ DLL จะสร้าง ไฟล์ `.md.js` DLLและLIB`.md.js` LIBไฟล์ `.md.js` (ไลบรารีนำเข้า) ใช้สำหรับการเชื่อมโยงกับ DLL ในระหว่างการคอมไพล์ ไม่จำเป็นสำหรับการเชื่อมโยงในขณะรันไทม์ เว้นแต่ว่า DLL นั้นจะเป็น เซิร์ฟเวอร์ Component Object Model (COM) DLLไฟล์ `.md.js` จะต้องอยู่ในไดเร็กทอรีใดไดเร็กทอรีหนึ่งที่ระบุไว้ในตัวแปรสภาพแวดล้อม PATH ในไดเร็กทอรีระบบเริ่มต้น หรือในไดเร็กทอรีเดียวกับโปรแกรมที่ใช้งาน เซิร์ฟเวอร์ DLL ของ COM จะถูกลงทะเบียนโดยใช้ regsvr32.exe ซึ่งจะบันทึกตำแหน่งของ DLL และรหัสเฉพาะทั่วโลก ( GUID ) ลงในรีจิสทรี จากนั้นโปรแกรมสามารถใช้ DLL ได้โดยการค้นหา GUID ในรีจิสทรีเพื่อค้นหาตำแหน่ง หรือสร้างอินสแตนซ์ของอ็อบเจ็กต์ COM โดยอ้อมโดยใช้ตัวระบุคลาสและตัวระบุอินเทอร์เฟซ
ตัวอย่างการเขียนโปรแกรม
การใช้การนำเข้า DLL
ตัวอย่างต่อไปนี้แสดงวิธีการใช้การผูกค่าเฉพาะภาษาเพื่อนำเข้าสัญลักษณ์สำหรับการเชื่อมโยงกับ DLL ในระหว่างการคอมไพล์
เดลฟี
{$APPTYPE CONSOLE}ตัวอย่างโปรแกรม;// นำเข้าฟังก์ชันที่บวกเลขสองจำนวนฟังก์ชันAddNumbers ( a , b : Double ) : Double ; StdCall ; external 'Example.dll' ;// โปรแกรมหลักvar R : Double ;begin R := AddNumbers ( 1 , 2 ) ; Writeln ( 'ผลลัพธ์คือ: ' , R ) ; end .ซี
ไฟล์ 'Example.lib' ต้องถูกรวมไว้ในโปรเจ็กต์ก่อนการเชื่อมโยงแบบคงที่ (โดยสมมติว่าไฟล์ Example.dll ถูกสร้างขึ้นแล้ว) ไฟล์ 'Example.lib' จะถูกสร้างขึ้นโดยอัตโนมัติโดยคอมไพเลอร์เมื่อคอมไพล์ DLL การไม่ดำเนินการตามคำสั่งข้างต้นจะทำให้เกิดข้อผิดพลาดในการเชื่อมโยง เนื่องจากตัวเชื่อมโยงจะไม่ทราบว่าจะหาคำจำกัดความของAddNumbersไฟล์ 'Example.lib' ได้จากที่ใด นอกจากนี้ อาจต้องคัดลอกไฟล์ DLL 'Example.dll' ไปยังตำแหน่งที่ไฟล์ .exe จะถูกสร้างขึ้นโดยใช้โค้ดต่อไปนี้:
#include <windows.h> #include <stdio.h>// นำเข้าฟังก์ชันที่บวกเลขสองจำนวนextern "C" __declspec ( dllimport ) double AddNumbers ( double a , double b );int main ( int argc , char * argv []) { double result = AddNumbers ( 1 , 2 ); printf ( "ผลลัพธ์คือ: %f \n " , result ); return 0 ; }การใช้การเชื่อมโยงรันไทม์แบบชัดเจน
ตัวอย่างต่อไปนี้แสดงวิธีการใช้ฟังก์ชันการโหลดและการเชื่อมโยงขณะรันไทม์ โดยใช้การผูก API ของ Windows ที่เฉพาะเจาะจงสำหรับแต่ละภาษา
โปรดทราบว่าตัวอย่างทั้งสี่ตัวอย่างมีความเสี่ยงต่อการโจมตีการโหลด DLLล่วงหน้า เนื่องจาก example.dll สามารถถูกระบุไปยังตำแหน่งที่ผู้เขียนไม่ได้ตั้งใจ (ไดเร็กทอรีการทำงานปัจจุบันจะอยู่ก่อนตำแหน่งไลบรารีของระบบ) และด้วยเหตุนี้จึงอาจนำไปสู่ไลบรารีเวอร์ชันที่เป็นอันตราย ดูข้อมูลอ้างอิงสำหรับคำแนะนำของ Microsoft เกี่ยวกับการโหลดไลบรารีอย่างปลอดภัย: ควรใช้SetDllDirectoryWin kernel32เพื่อลบการค้นหาไดเร็กทอรีปัจจุบันก่อนที่จะโหลดไลบรารีใดๆ[ 9 ]
ไมโครซอฟต์ วิชวลเบส
Option Explicit Declare Function AddNumbers Lib "Example.dll" _ ( ByVal a As Double , ByVal b As Double ) As DoubleSub Main () Dim Result As Double Result = AddNumbers ( 1 , 2 ) Debug . Print "ผลลัพธ์คือ: " & Result End Subเดลฟี
โปรแกรมตัวอย่าง; {$APPTYPE CONSOLE} ใช้Windows ; var AddNumbers : ฟังก์ชัน( a , b : จำนวนเต็ม) : Double ; StdCall ; LibHandle : HMODULE ; begin LibHandle := LoadLibrary ( 'example.dll' ) ; if LibHandle <> 0 then AddNumbers := GetProcAddress ( LibHandle , 'AddNumbers' ) ; if Assigned ( AddNumbers ) then Writeln ( '1 + 2 = ' , AddNumbers ( 1 , 2 ) ) ; Readln ; end .ซี
#include <windows.h> #include <stdio.h>// ลายเซ็นฟังก์ชัน DLL typedef double ( * importFunction )( double , double );int main ( int argc , char ** argv ) { importFunction addNumbers ; double result ; HINSTANCE hinstLib ;// โหลดไฟล์ DLL hinstLib = LoadLibrary ( TEXT ( "Example.dll" )); if ( hinstLib == NULL ) { printf ( "ERROR: ไม่สามารถโหลด DLL ได้\n " ); return 1 ; }// รับพอยเตอร์ฟังก์ชันaddNumbers = ( importFunction ) GetProcAddress ( hinstLib , "AddNumbers" ); if ( addNumbers == NULL ) { printf ( "ERROR: unable to find DLL function \n " ); FreeLibrary ( hinstLib ); return 1 ; }// เรียกฟังก์ชันผลลัพธ์= addNumbers ( 1 , 3 );// ยกเลิกการโหลดไฟล์ DLL FreeLibrary ( hinstLib );// แสดงผลลัพธ์printf ( "ผลลัพธ์คือ: %f \n " , result );ส่งคืนค่า0 ; }ไพธอน
การเชื่อมต่อ ctypes ของ Python จะใช้ API ของ POSIX บนระบบ POSIX
นำเข้าctypesmy_dll = ctypes ซีดี. LoadLibrary ( "Example.dll" )# จำเป็นต้องระบุเมธอด "restype" ต่อไปนี้เพื่อให้# Python เข้าใจ ว่าฟังก์ชันส่งคืนค่าประเภทใดmy_dll.AddNumbers.restype = ctypes.c_doublep = my_dll.AddNumbers ( ctypes.c_double ( 1.0 ) , ctypes.c_double ( 2.0 ) )พิมพ์( "ผลลัพธ์คือ:" , หน้า)โมเดลวัตถุส่วนประกอบ
โมเดลวัตถุคอมโพเนนต์ (COM) กำหนดมาตรฐานไบนารีเพื่อรองรับการใช้งานวัตถุในไฟล์ DLL และ EXE โดยมีกลไกในการค้นหาและกำหนดเวอร์ชันของไฟล์เหล่านั้น รวมถึงคำอธิบายอินเทอร์เฟซที่ไม่ขึ้นกับภาษาและสามารถอ่านได้ด้วยเครื่อง การโฮสต์วัตถุ COM ใน DLL นั้นมีน้ำหนักเบากว่าและช่วยให้สามารถแบ่งปันทรัพยากรกับกระบวนการไคลเอ็นต์ได้ ซึ่งทำให้วัตถุ COM สามารถใช้งานแบ็กเอนด์ที่มีประสิทธิภาพไปจนถึงฟรอนท์เอนด์ GUI ที่เรียบง่าย เช่น Visual Basic และ ASP นอกจากนี้ยังสามารถเขียนโปรแกรมจากภาษาสคริปต์ได้อีกด้วย[ 10 ]
การโจรกรรม DLL
เนื่องจากช่องโหว่ที่รู้จักกันทั่วไปในชื่อ DLL hijacking, DLL spoofing, DLL preloading หรือ binary planting ทำให้โปรแกรมจำนวนมากสามารถโหลดและเรียกใช้ DLL ที่เป็นอันตรายซึ่งอยู่ในโฟลเดอร์เดียวกับไฟล์ข้อมูลที่โปรแกรมเหล่านั้นเปิด[ 11 ] [ 12 ] [ 13 ] [ 14 ]ช่องโหว่นี้ถูกค้นพบโดย Georgi Guninski ในปี 2000 [ 15 ] ในเดือนสิงหาคม 2010 ช่อง โหว่ นี้ได้รับความสนใจไปทั่วโลกหลังจากที่ ACROS Security ค้นพบอีกครั้ง และพบว่ามีโปรแกรมหลายร้อยโปรแกรมที่มีช่องโหว่[ 16 ] โปรแกรมที่ทำงานจากตำแหน่งที่ไม่ปลอดภัย เช่น โฟลเดอร์ที่ผู้ใช้สามารถเขียนได้ เช่นโฟลเดอร์ดาวน์โหลดหรือ โฟลเดอร์ Tempมักจะมีความเสี่ยงต่อช่องโหว่นี้[ 17 ] [ 18 ] [ 19 ] [ 20 ] [ 21 ] [ 22 ] [ 23 ]
ดูเพิ่มเติม
- Dependency Walkerคือยูทิลิตี้ที่แสดงฟังก์ชันที่ส่งออกและนำเข้าของไฟล์ DLL และ EXE
- ไลบรารีแบบไดนามิก
- ห้องสมุด (คอมพิวเตอร์)
- ตัวเชื่อม (คอมพิวเตอร์)
- ตัวโหลด (คอมพิวเตอร์)
- Moricons.dll
- ไฟล์ออบเจ็กต์
- ห้องสมุดส่วนกลาง
- ไลบรารีแบบคงที่
- DLL เฮลล์
ลิงก์ภายนอก
- dllexport, dllimportบน MSDN
- ไลบรารีการเชื่อมโยงแบบไดนามิกบน MSDN
- ความปลอดภัยของไลบรารีการเชื่อมโยงแบบไดนามิกบน MSDN
- ลำดับการค้นหาไลบรารีลิงก์แบบไดนามิกบน MSDN
- คำแนะนำด้านความปลอดภัยจาก Microsoft: การโหลดไลบรารีที่ไม่ปลอดภัยอาจทำให้เกิดการเรียกใช้โค้ดจากระยะไกลได้
- DLL คืออะไร? (ดูข้อมูลเพิ่มเติมได้ที่เว็บไซต์สนับสนุนของ Microsoft)
- ฟังก์ชันไลบรารีการเชื่อมโยงแบบไดนามิกบน MSDN
- ข้อกำหนดรูปแบบไฟล์ปฏิบัติการแบบพกพาและออบเจ็กต์ทั่วไปของ Microsoft
- ข้อกำหนดของ Microsoft สำหรับไฟล์ DLL
- การทิ้งระเบิดปูพรมและการวางยาพิษในสมุดรายชื่อ
- MS09-014: การแก้ไขช่องโหว่ Safari Carpet Bomb
- ข้อมูลเพิ่มเติมเกี่ยวกับช่องโหว่การโจมตีระยะไกลด้วยการโหลด DLL ล่วงหน้า
- ความคืบหน้าเกี่ยวกับช่องโหว่การโจมตีระยะไกลโดยการโหลด DLL ล่วงหน้า
- โหลดไลบรารีอย่างปลอดภัย
- DLL คืออะไร และจะแก้ไขได้อย่างไร