O co chodzi z DLL dependecy hell w .NET?

0

Jako że mam w dużym projekcie legacy problem z różnymi wersjami blibioteki Newtonsoft.Json co wymaga stosowania bindingRedirect w configu, próbuję zrozumieć o co w tym w ogóle chodzi.
screenshot-20240317132231.png
Źródło obrazka

Przeczytałem już kilka artykułów na ten temat i każdy z nich można streścić w zasadzie tak: problem pojawia się gdy w projekcie różne biblioteki zależą od tej samej paczki ale o różnych wersjach. Jedna z zależności wymaga paczki X w wersji 1, a druga w dajmy na to wersji 2. Z jakiegoś powodu możemy mieć w projekcie tylko jedną wersję, stąd właśnie użycie bindingRedirect i podstawianie bibliotekom innej zależnosci niż one faktycznie potrzebują.

Nie rozumiem natomiast jednej rzeczy: co nas właściwie powstrzymuje przed trzymaniem w pamięci dwóch wersji paczki jednocześnie? Dlaczego nie możemy trzymać kilku wersji Newtonsofta w binarkach projektu i używać każdej z nich według potrzeb? Może i będzie to kosztować więcej pamięci, ale czy to nie jest wciaż lepsze niż bindingRedirect który może potencjalnie spowodować błąd w czasie wykonania programu?

0

Strzelam, że problem z wersjami bierze się z tego powodu, że pliki niezależnie od wersji mają taką samą nazwę, a do jednego folderu nie wsadzi się 2 plików o takiej samej nazwie.

0

W sumie nic nie stoi na przeszkodzie, żeby mieć dwie różne wersje załadowane bibliotek, ale problem chyba jest z tym, że jak wyżej, nazwa jest taka sama biblioteki i jak aktualizują to zamiana jest starszej wersji na nową.

W sumie są 3 metody ładowania bibliotek, static i implicit nie umożliwiają takiej swobody, ale explicit gdzie sam wybierasz bibliotekę i funkcję wewnątrz jej to już bez problemu rozdzielisz..

Ale na pewno będzie kolizja nazw funkcji z ich parametrami w normalym przypadku, np. sum int int, w elf64 i exe plikach, nazwy funkcji można sprawdzić w global symbols nie mogą być takie same dwa symbole czyli jak mamy sum double double to jest coś innego niż sum int int i jest to rozróżnialne, ale dopiero od C++ w C nie dodali rozróżnienia, w C jest zapisana tylko nazwa funkcji, w C++ już nazwa funkcji i jej typy parametrów, więc pozwala to na większą swobodę rozróżnienia funkcji.
No i C++ pozwala jeszcze na namespace, gdzie wtedy można jeszcze udać, że to zupełnie inna biblioteka.
Czyli C++ robi zwykły mangling nazw.

Explicit dll loading to jeśli rekurencyjne ładowanie wykreślimy to jest to zwykłe odczytanie pliku do pamięci i skok do adres gdzie załadowaliśmy plik dll + offset do funkcji i uruchomienie kodu to nic innego jak zwykłe wywołanie tej funkcji. Oczywiście ona może rekurencyjnie inne biblioteki potrzebować.

Jakby się ktoś uparł to da się to zrobić.

2

Jak najbardziej można mieć w jednej app domenie wczytane dwie wersje biblioteki pod warunkiem, że są podpisane i mają silną nazwę. Schody się zaczynają kiedy w grę wchodzi jakaś własna implementacja AssemblyResolve czy właśnie redirecty, i te referencje zostaną pomieszane w trakcie wykonania.

0

A jest jakiś powód, dla którego dll nie generują się z wersją w nazwie? Nie byłoby wtedy konfliktów

0
west napisał(a):

A jest jakiś powód, dla którego dll nie generują się z wersją w nazwie? Nie byłoby wtedy konfliktów

Niektóre projekty tak robią, np DevExpress. Ale wtedy masz konflikty w przestrzeniach nazw i nadal ciężko jest używać dwóch wersji na raz, rzadko kiedy ma to zresztą sens. Poza tym przeczy to idei DLL-ek i tego że powinny być podmienialne na nowsze wersje i kompatybilne wstecz. Nie wiem czy wklejenie bindingRedirect to aż taki problem. W praktyce w sumie się spotkałem tylko z takim problemem z Newtonsoft.Json, zazwyczaj żadna DLL-ka nie jest używana w różnych wersjach przez różne zależności lub nie mają strong namingu, bo trzeba dodać że ten problem dotyczy tylko dllek ze strong namingiem. Ten problem też odchodzi w zasadzie bo wbudowana serializacja Json jest wystarczająco dobra i coraz mniej będzie się używało Newtonsoftu, a problem ten dotyczy też tylko .NET Frameworka.

iteredi napisał(a):

Nie rozumiem natomiast jednej rzeczy: co nas właściwie powstrzymuje przed trzymaniem w pamięci dwóch wersji paczki jednocześnie? Dlaczego nie możemy trzymać kilku wersji Newtonsofta w binarkach projektu i używać każdej z nich według potrzeb? Może i będzie to kosztować więcej pamięci, ale czy to nie jest wciaż lepsze niż bindingRedirect który może potencjalnie spowodować błąd w czasie wykonania programu?

Wręcz taka jest idea strong namingu, cytując https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/strong-naming

The benefits of strong naming on .NET Framework are:
The assembly can be loaded side by side with other versions of the assembly. Side-by-side assembly loading is commonly required by applications with plug-in architectures.

Problemem jest to że by default wszystkie zależności ładują inne DLL-ki z tego samego miejsca i wtedy musisz własnym assembly resolverem wskazać właściwą wersję zależności a jako że domyślnie wszystko się ładuje do jednego folderu to są konflikty. Jeżeli masz pluginy w DLL i każdy trzyma obok siebie w folderze właściwą wersję Newtonsoft.Json to wszystko będzie działało poprawnie

1 użytkowników online, w tym zalogowanych: 0, gości: 1