DLL hell

A DLL-hell (DLL pokol) egy színes kifejezés arra a helyzetre, amikor a Windows operációs rendszer képtelen helyesen kezelni a telepített DLL-eket (dinamikus linkelésű könyvtárakat).  Az általánosabb verziópokol speciális esete. Ennek több oka is lehet:
 A futtatott DLL számára szükséges másik DLL nem található vagy inkompatibilis a verziója;
 Ugyanannak a DLL-nek több verziója is fenn van a rendszeren;

Problémák
A DLL-ek lényege, hogy több program is használhatja ugyanazokat az eljárásokat, így memóriát és lemezterületet takarítva meg, valamint a programok készítése is egyszerűsödik, mivel ugyanazt a rutineljárást csak egyszer kell elkészíteni. A statikus könyvtárakat az egyes programok tartalmazzák, így növelik azok méretét. Azonban, ha egy új program úgy telepít egy DLL-t, hogy felülírja annak régebbi változatát, ez eredményezheti, hogy régebb telepített programok (amelyek a régi DLL-t használták) többet nem fognak futni, vagy hibásan működnek. 

A DLL-ekbe nincs beépítve a visszafelé kompatibilitás. Emiatt még a kisebb változások miatt is olyan DLL fordulhat, melynek belső szerkezete annyira különbözik az előzőektől, hogy a korábbi verziókat használó programok összeomlanak. A statikus könyvtárakkal nincs ilyen probléma, mivel azok együtt vannak a program többi részével, egy egységben.

A DLL-ek szerkezetének változása azért olyan fontos, mivel a kliens programok a bennük található adattípusokat, függvényeket és eljárásokat aszerint hivatkozzák, hogy hányadikként szerepelnek a DLL-ekben. 

Okozhatja a káoszt az is, ha egy alkalmazás nem törli le a csak általa használt DLL-t, mikor a rendszerből eltávolítják. Konfliktusba kerülhetnek különböző verziók vagy nehéz lehet megszerezni a szükséges DLL-eket.

Extrém esetben ez az operációs rendszer teljes összeomlását is eredményezheti: a Microsoft Windows rendszerekben ez kék halálként ismeretes.

A Microsoft már előzetesen megpróbált megoldást találni. A .Net keretrendszerben az assemblyk nyújtanak lehetséges alternatívát.

Összeférhetetlenség
Egy DLL adott verziója megfelelő lehet egyes programok számára, míg mások számára nem. A Windows is sebezhetővé vált, mivel támogatta a dinamikus linkelést a C++ könyvtárak és az  Object Linking and Embedding (OLE) objektumok között. A C++  osztályok exportálják metódusaikat, de például már az is inkompatibilitást okozhat, hogy hozzáadnak egy új metódust vagy virtuálissá tehetnek egy már létezőt. Ezt az Object Linking and Embedding megpróbálja úgy kivédeni, hogy az interfészeknek stabilnak kell maradniuk, és a memóriakezelők nem közösek. Ez azonban nem elégséges, mivel a szemantika is változhat. Egy alkalmazás javítása okozhatja azt, hogy egy másikban elvész egy képesség. A Windows 2000 előtti Windowsok sebezhetőségét okozta, hogy közösek voltak a COM osztálytáblák a felhasználók és a folyamatok között. Egy DLL/EXE csak egy COM-ot definiálhatott; ha egy programnak szüksége volt ennek egy példányára, akkor mindig csak a központit kaphatta. Emiatt, ha egy új program a DLL új verzióját telepítette, azzal használhatatlanná tehette a gépen már meglevő programokat.

Régebbi verziók
Nemcsak az újabb, hanem a korábbi verziók is okozhatnak problémát. A  Windows 3.1-ben gyakori probléma volt a ctl3d.dll és a ctl3dv2.dll felülírása. Ezeket a Microsoft hozta létre, de a telepített programok nem az aktuális, hanem a korábbi verziókkal érkeztek, és írták felül. Okai:
 A Microsoft a múltban futásidejű DLL-eket osztott meg, mint megosztott rendszerkomponenseket (eredetileg C:\WINDOWS és C:\WINDOWS\SYSTEM). Ezzel próbáltak takarékoskodni a hellyel és a memóriával. Ezt a többi fejlesztő csak lemásolta.
 A telepítők rendszerint privilegizált szinten futnak, hogy jogosultak arra, hogy DLL-eket telepítsenek rendszerkönyvtárba, DLL-eket és COM-okat regisztráljanak. Egy nem megfelelően működő telepítő emiatt a rendszer DLL-eket legacy verziókkal írhatja felül. Ezen nem segíthet sem a Windows File Protection, sem a Windows Resource Protection. A Windows Vistától kezdve csak megbízható telepítő szerezhet ilyen jogosultságot.
 A Windows alkalmazások jogosultak voltak arra, hogy telepítőjük frissítéseket végezzen az operációs rendszeren. Sok Microsoft DLL szabadon terjeszthető volt, saját verziójukat telepítették.
 A Windows Installer előtt a telepítőket a fejlesztők készítették, így akár figyelmetlenségből vagy tapasztalatlanságból is elronthatták a verziók kezelését. A fejlesztőkörnyezetek sem mind adták hozzá automatikusan a megfelelő verziót. Csak arra volt lehetőség, hogy ellenőrizzék az adatokat, felülírják a fájlokat vagy kihagyják a felülírást, ha már telepítve van a DLL.
 Néha az operációs rendszer is régebbiekre cserélt DLL-eket, amelyek akár elavultak is lehettek. Így például ha egy színes nyomtató után fekete-fehér nyomtatót telepítettek, akkor a színeket ismerő nyomtató DLL-eket felülírta színeket nem ismerő DLL-ekkel.

Inkorrekt COM regisztráció
A side-by-side assemblyk előtt a Registry döntött arról, hogy melyik DLL-t kell használni. Ha a modul egy másik verziója van regisztrálva, akkor az fog betöltődni, és nem az, amit a felhasználó elvár. Okozhatják ütköző telepítések, amelyek ugyanabba  a könyvtárba települnek; ekkor az utolsó telepítés verziója érvényes.

Megosztott modulok a memóriában
A 16 bites Windowsok (és a Windows on Windows) egy adott DLL egy példányát töltik be. Minden alkalmazás ezt használja. Ha már nincs rá szükség, akkor törlődik a memóriából. A többi (32 illetve 64 bites) Windowson csak akkor osztoznak programok egy példányon, ha ugyanazt a megosztott DLL-t töltötték be. Így, ha egy program már betöltötte a modult egy másik könyvtárban, akkor nem indítható egy másik program, ami egy ezzel összeférhetetlen verziót használ. Ehhez előbb az összes, az ütköző verziót használó programot be kell zárni, és utána ezt elindítani. Előfordulhat, hogy ezután a másik program újra elindítható; így a probléma csak akkor jelentkezik, ha nem a megfelelő sorrendbe indították az alkalmazásokat.

Használhatóság
Ha egy DLL frissítése nem rontja el az összes programot, ami használja, akkor nehezebb a DLL verziójával kapcsolatos problémákat kezelni. A biztonsági frissítések sürgető és fájdalmas esetek. Ahelyett, hogy csak a legújabb verziót vizsgálná, minden korábbi verziót is tesztelni kell összeférhetőség szempontjából.

Okai
A DLL-ek között konfliktust okozhatnak a következők:
 A memória korlátjai, 16 bites Windowsoknál az, hogy a folyamatoknak nincs külön memóriatere.
 A DLL-ekbe nincs beépítve a visszafelé kompatibilitás. Nincs konvenció a verziókra, nincsenek szabványok az elnevezésekre, hiányoznak a fájlrendszer lokalizációs szabályok.
 Nincs kikényszeríthető szabványos módszer a telepítésre és eltávolításra (csomagkezelés)
 Nincs központosított támogatás a DLL bináris alkalmazói interfészek kezelésére és védelmére, emiatt lehetnek összeférhetetlen DLL verziók ugyanazzal a névvel és belső verziószámozással.
 Túlegyszerűsített kezelőeszközök, amelyek nem segítik, hogy a felhasználók megtalálják, hogy melyik DLL okozta a problémát.
 Az elosztott modulok fejlesztői megtörhetik a visszafelé kompatibilitást.
 Az operációs rendszer futásidejű komponenseinek frissítési rendszere.
 A régebbi Windows verziók képtelensége arra, hogy több verziót futtassanak egy adott könyvtárból.
 Az aktuális könyvtárra vagy a %PATH% környezeti változóra hagyatkozás, amikor a DLL-eket keresi a gép. 
 A fejlesztőknek meg van engedve, hogy újrahasználják alkalmazásaik COM interfészeinek minta alkalmazásainak ClassID-jét, holott generálhatnának új GUID-eket.

A Windows NT előtti verziókban gyakori jelenség volt a DLL-pokol. Ez főként amiatt volt, hogy a 16 bites Windowsok nem különítették el az egyes alkalmazások által használt memóriát, emiatt nem tölthették be saját verziójukat az osztott könyvtárra. A telepítőktől elvárták, hogy verifikálják DLL-információjukat, mielőtt felülírják a rendszer DLL-eket. A Microsoft és mások különböző eszközöket adtak a fejlesztések megkönnyítésére, amelyek magukkal vitték a rendszer DLL-eket. A terjesztőknek szabvány telepítőt kellett használniuk, aminek megfelelően kellett működnie, mielőtt megkapták a Microsoft logót. A jó polgár megközelítés nem segítette a probléma kezelését, mivel az Internet terjedése miatt számos más programot lehetett letölteni és telepíteni, amelyek nem feltétlenül viselkedtek jó polgár módjára.

A problémák nagy része még azóta is fennáll, amit kihasználnak a kártevők készítői is. Ez további sebezhetőséget jelent, ami érint minden programterjesztőt, aki Windowsra ad ki programokat, köztük a Microsoftot is.

Megoldások
A probléma kezelésére számos megoldást gondoltak ki.

Statikus linkelés
A statikus linkelés megkerüli a problémát azzal, hogy egyáltalán nem használ dinamikus linkelést. C/C++ alkalmazásoknál gyakori, hogy ahelyett, hogy amiatt kellene aggódni, hogy milyen verziója van telepítve például a MFC42.DLL könyvtárnak, az alkalmazás statikusan linkeli ugyanazokat a könyvtárakat. Ezzel viszont csak azokra a könyvtárakra korlátozza magát, melyek statikusan linkelhetők. Ez a megoldás feláldozza azt a célt is, amire a DLL-eket használják: hogy csökkentse a memóriahasználatot. A könyvtári kód duplikálásával a szoftverek mérete megnő. Bonyolultabbá válnak a biztonsági frissítések és a függőségek új verzióinak telepítése.

Windows File Protection
A DLL felülírási problémát mérsékli a Windows File Protection, amit a Windows 2000-ben vezették be. Ez megelőzi, hogy nem igazolt alkalmazások felülírjanak rendszer DLL-ek, hacsak nem használnak bizonyos meghatározott Windows API-t, ami ezt megengedi. Továbbra is fennáll az a kockázat, hogy a Microsoft frissítések összeférhetetlenek egyes alkalmazásokkal, de ezt a kockázatot csökkenti a side-by-side assemblyk használata.

Más telepítők nem írhatják felül a rendszer DLL-eket, hacsak nem kötik be a Windows updatert is. Lehetséges még a Windows File Protection letiltása, vagy a Windows Vistától kezdődően a fájlrendszer birtokba vétele és saját maguk feljogosítása. Ezeket a változásokat azonban a SFC vissza tudja állítani.

Több DLL párhuzamos használata
Ebben a megközelítésben az ütközéseket úgy oldják fel, hogy a vitatott DLL-ből egy vagy több program saját verziót tart.

Kézi megoldásként a 32 és 64 bites programoknál megtehető, hogy a megfelelő DLL-t a program mappájába másoljuk. Ekkor a program el tud indulni más programok előtt és után is. 16 bites programok esetén ez nem tehető meg, csak különböző virtuális gépek használatával. A Windows 98 SE/2000 előtt ezt az OLE felügyelte, így egyszerre egy DLL-nek egy verziója lehetett betöltve. 

A Windows 98 SE/2000 bevezette a side-by-side assembly lehetőségét. Ez képes több különböző verziójú DLL-t betölteni, lehetővé téve, hogy az ugyanazt a DLL-t betöltő programok továbbra is ugyanazokat az objektumokat használják, így memóriát takarítva meg. Hátránya, hogy az árva DLL-eket nem fogja karban tartani az automatikus folyamatok.

Hordozható alkalmazások
Futásidejű környezettől és architektúrától függően, a hordozható alkalmazások csökkenthetik a DLL által okozott problémákat azzal, hogy a program egy csomagban tartalmazza a DLL-eket is. Ekkor a program nem minősíti a teljes elérési utat, hanem arra a mechanizmusra hagyatkozik, ami először helyben keresi a szükséges adatokat és programokat. Azonban ezt is kihasználhatják rosszindulatú programok. A nagyobb rugalmasság ára a biztonság romlása, ha a magán DLL-ek nem kapnak biztonsági frissítéseket. 

Az alkalmazásvirtualizáció lehetővé teszi, hogy minden alkalmazás a saját kis világában fusson, így ne férhessen hozzá közvetlenül a fájlrendszerben levő fájlokhoz.

További ellenintézkedések
 A népszerű Microsoft Visual Studio fejlesztőkörnyezethez hozzá vannak kapcsolva installációs eszközök. Ezek az eszközök ellenőrzik a DLL-ek verzióit, és csatolhatnak már létező telepítőcsomagokat a programokhoz. Így a programok úgy integrálhatják az operációs rendszer komponensfrissítését, hogy telepítőt kellene írniuk hozzá.
 A rendszer-visszaállítás segít eltüntetni a rossz telepítések által okozott károkat. Habár ez nem segít elkerülni a problémákat, de megkönnyíti azok megoldását.
 A WinSxS könyvtár lehetővé teszi, hogy egy könyvtárból egyszerre több verzió is együtt létezzen.
 32 bites rendszeren a 16 bites alkalmazások külön memóriatérben való futtatása, így különböző verziójú DLL-eket használhatnak megkötések nélkül.
 A  Windows Me óta a Windowsok védik rendszerfájljaikat a változásoktól. A  Windows Me és Windows 2000 bevezette a Windows File Protectiont, és ezt használta a Windows XP és a Windows Server 2003 is. Ezt a Windows Vistától a  Windows Resource Protection váltotta fel.
 A regisztrációt nem igénylő COM-ot a Windows XP vezette be. Ez lehetővé teszi, hogy minden program a saját könyvtárában tárolja a COM regisztrációját, ahelyett, hogy egy központi COM lenne. Így az alkalmazások saját verzióval bírhatnak, amit a Mocrosoft "Side-by-Side Assembly"nek nevez. Ez azonban nem teljesül az EXE COM szerverekre vagy az olyan összetevőkre, mint az MDAC, MSXML, DirectX vagy az Internet Explorer. 
 A Windows Me és a Windows 2000 óta a  Windows Installer fejlettebb csomagkezelést végez, így nyomon követi a DLL függőséget. Ennek használatát bátorítani is kellett, és lebeszélni a felhasználókat a kézi telepítésről.
 Egy központi adatbázis is elvégezheti a DLL konfliktus megoldását. Ez ellenőrzi, hogy egy DLL-nél fennáll-e a kompatibilitás a különböző verziók között. Inkompatibilitás esetén a hozzáadhat egy adapter interfészt a régebbi programok számára, vagy külön csomagként telepítheti.
 Ha egy programozó vagy egy csapat megváltoztatja az általa használt könyvtárat, akkor választhatja a statikus linkelést vagy magával viheti a saját DLL-jét.
 Modularizáció szempontjából ugyan a DLL a legjobb, de statikus linkeléssel gyorsabb program készíthető. Modern rendszereken nem olyan kritikus a memória mennyisége, mint régen. 
 A Windows Vistától kezdve a Windowsra a TrustedInstaller speciális szolgáltatás telepíti az operációs rendszer fájljait. Más felhasználók, még a SYSTEM sem írhatja felül a rendszerfájlokat. A Windows 7 ezt a védelmet a Registry egyes részeire is kiterjeszti.
 A webes alkalmazások azzal kerülik meg a problémát, hogy szerveren futnak egy külön buborékban, és a böngészőn keresztül kommunikálnak a klienssel.

Hasonló problémák más operációs rendszerek alatt 
A "DLL hell", magyarul "DLL pokol" szakkifejezés definíció szerint kizárólag Windows operációs rendszerre vonatkozhat, mivel a több processz által is használható megosztott eljáráskönyvtárakat egyedül itt hívják DLL-nek, ugyanis eredetileg ez a kiterjesztés tartozott hozzájuk.

Ugyanakkor megosztott eljáráskönyvtárak minden modernebb operációs rendszerben előfordulnak, csak a nevük más. Például Linux/Unix rendszerekben shared lib a nevük. A "DLL hell" probléma megfelelője elvileg velük kapcsolatban is előállhat, ám a ma használatos Linux rendszerek esetén több tényező is gátat vet neki:
 Linux (és a legtöbb Unix) alatt a kernel és a userspace folyamatok mind koncepcionálisan, mind strukturálisan élesen elhatárolódnak. Teljes rendszerleállást csak kernelhiba tud okozni, ám a shared libek teljes mértékben userspace-ben vannak. Ezért bármilyen shared lib-eredetű programhiba csak egyes programok hibás működését tudja okozni, magát a rendszert nem állíthatja le.
 A rendszer nyílt forráskódú volta miatt az egyes shared libek kezelői felülete is teljes mértékben megismerhető, és általában jól is dokumentált, ezért könnyebb őket a különböző verzióik között egymással kompatibilisre megírni
 A rendszer dinamikus linkere felhasználóként, akár futásidőben is beállítható, ezért nagyon könnyen kivitelezhető, hogy az egyes szoftverek más és más shared libeket használjanak. Erre szolgáló mechanizmus később a Windows-ban is megjelent (GAC: Global Assembly Cache).

Jegyzetek

Források
 Getting Out of DLL Hell on Microsoft TechNet
 Simplifying Deployment and Solving DLL Hell with the .NET Framework on MSDN
 Avoiding DLL Hell: Introducing Application Metadata in the Microsoft .NET Framework by Matt Pietrek
 Dr. Dobb's on DLL Hell (details on LoadLibraryEx)
 Joel on Software discussion
 Article on DLL Hell

Fordítás

Informatika