問題
DLL是Microsoft的共享庫實現。共享庫允許將公共代碼捆綁到包裝器DLL中,並由系統上的任何應用程式軟體使用,而無需將多個副本載入到記憶體中。一個簡單的例子可能是GUI文本編輯器,它被許多程式廣泛使用。通過將此代碼放在DLL中,系統上的所有應用程式都可以使用它而無需使用更多記憶體。這與靜態庫形成對比,靜態庫功能相似,但將代碼直接複製到應用程式中。在這種情況下,每個應用程式都會增加它使用的所有庫的大小,對於現代程式來說這可能非常大。
當計算機上的DLL版本與創建程式時使用的版本不同時,會出現問題。 DLL沒有用於向後兼容的內置機制,甚至對DLL的微小更改使其內部結構與以前的版本不同,嘗試使用它們通常會導致應用程式崩潰。靜態庫避免了這個問題,因為用於構建應用程式的版本包含在其中,因此即使系統上的其他位置存在較新的版本,這也不會影回響用程式。
版本不兼容的一個關鍵原因是DLL檔案的結構。該檔案包含DLL中包含的各個方法(過程,例程等)的目錄以及它們獲取和返回的數據類型。即使對DLL代碼進行微小的更改也可能導致重新排列此目錄,在這種情況下,調用特定方法的應用程式認為它是目錄中的第4項可能最終會調用完全不同且不兼容的例程,這會通常會導致應用程式崩潰。
DLL經常遇到一些問題,特別是在系統上安裝和卸載了許多應用程式之後。困難包括DLL版本之間的衝突,難以獲得所需的DLL,以及具有許多不必要的DLL副本。
原因
DLL不兼容的原因是:
記憶體限制,以及16位版本的Windows中缺少進程記憶體空間的分離;
缺乏針對DLL的強制標準版本控制,命名和檔案系統位置模式;
缺乏用於軟體安裝和刪除的強制標準方法(包管理);
缺乏對DLL應用程式二進制接口管理和安全措施的集中權威支持,允許發布具有相同檔案名稱和內部版本號的不兼容DLL;
過度簡化的管理工具,防止用戶和管理員識別已更改或有問題的DLL;
開發人員破壞共享模組中函式的向後兼容性;
Microsoft發布對作業系統運行時組件的帶外更新;
早期版本的Windows無法運行同一個庫的並行衝突版本;
依賴於當前目錄或%PATH%環境變數,它們隨時間和系統而變化,以查找相關的DLL(而不是從顯式配置的目錄中載入它們);
開發人員將示例應用程式中的ClassID重用於其應用程式的COM接口,而不是生成自己的新GUID。
DLL Hell在Windows NT之前版本的Microsoft作業系統上是一種非常普遍的現象,主要原因是16位作業系統沒有將進程限制到自己的記憶體空間,因此不允許它們載入自己的版本的他們兼容的共享模組。在覆蓋現有系統DLL之前,應用程式安裝程式應該是好公民並驗證DLL版本信息。 Microsoft和其他第三方工具供應商提供了簡化應用程式部署的標準工具(始終涉及運送相關的作業系統DLL)。在授予使用Microsoft徽標之前,Microsoft甚至要求應用程式供應商使用標準安裝程式並使其安裝程式經過認證,以使其正常工作。良好的公民安裝方法並沒有緩解這個問題,因為網際網路普及的增加為獲得不合格的應用程式提供了更多的機會。
解決方案
多年來,各種形式的DLL地獄已經被解決或減輕。
靜態連結
應用程式中DLL Hell的一個簡單解決方案是靜態連結所有庫,即包含程式中所需的庫版本,而不是選擇具有指定名稱的系統庫。這在C / C ++應用程式中很常見,其中不必擔心安裝了哪個版本的MFC42.DLL,而是將應用程式編譯為靜態連結到相同的庫。這完全消除了DLL,並且可以在僅使用提供靜態選項的庫的獨立應用程式中實現,就像Microsoft基礎類庫一樣。然而,犧牲了DLL的主要目的,即程式之間的運行時庫共享以減少記憶體開銷。在多個程式中複製庫代碼會導致軟體膨脹,並使安全修復程式或更新版本的相關軟體的部署變得複雜。
Windows檔案保護
使用Windows檔案保護(WFP)在Windows 2000中引入了DLL覆蓋問題(微軟稱為DLL Stomping)。這可以防止未經授權的應用程式覆蓋系統DLL,除非它們使用允許此操作的特定Windows API。可能仍存在Microsoft的更新與現有應用程式不兼容的風險,但通過使用並排程式集,當前版本的Windows通常會降低此風險。
第三方應用程式無法踩踏作業系統檔案,除非它們將合法的Windows更新與其安裝程式捆綁在一起,或者如果它們在安裝期間禁用了Windows檔案保護服務,並且在Windows Vista或更高版本中也取得系統檔案的所有權並授予自己訪問許可權。 SFC實用程式可以隨時還原這些更改。
同時運行衝突的DLL
這裡的解決方案包括為磁碟和記憶體中的每個應用程式提供相同DLL的不同副本。
一個簡單的手動解決方案是將問題DLL的不同版本放入應用程式的資料夾中,而不是通用的系統範圍資料夾。只要應用程式是32位或64位,並且DLL不使用共享記憶體,這通常可以正常工作。對於16位應用程式,這兩個應用程式不能在16位平台上同時執行,也不能在32位作業系統下的同一個16位虛擬機中同時執行。 OLE在Windows 98 SE / 2000之前阻止了這一點,因為早期版本的Windows為所有應用程式都有一個COM對象註冊表。Windows 98 SE / 2000引入了一種稱為並行程式集的解決方案,它為每個需要它們的應用程式載入單獨的DLL副本(從而允許需要衝突的DLL的應用程式同時運行)。這種方法通過允許應用程式將模組的唯一版本載入到其地址空間中來消除衝突,同時保留通過使用記憶體映射技術在仍然執行的不同進程之間共享公共代碼來共享應用程式之間的DLL(即減少記憶體使用)的主要好處使用相同的模組。然而,在多個進程之間使用共享數據的DLL不能採用這種方法。一個負面影響是DLL的孤立實例可能無法在自動化過程中更新。
攜帶型套用
根據應用程式體系結構和運行時環境,可移植應用程式可能是減少某些DLL問題的有效方法,因為每個程式都捆綁了自己所需的任何DLL的私有副本。該機制依賴於應用程式在載入它們時沒有完全限定依賴DLL的路徑,並且作業系統在任何共享位置之前搜尋可執行目錄。然而,這種技術也可以被惡意軟體利用,如果私有DLL沒有以與共享DLL相同的方式保持最新的安全補丁,則增加的靈活性也可能以犧牲安全性為代價。
應用程式虛擬化還允許應用程式在“泡沫”中運行,這避免了將DLL檔案直接安裝到作業系統的檔案系統中。