Różnica między obiektami współdzielonymi (.so), bibliotekami statycznymi (.a) i bibliotekami DLL (.so)?


272

Brałem udział w debacie dotyczącej bibliotek w systemie Linux i chciałbym potwierdzić pewne rzeczy.

W moim rozumieniu (popraw mnie, jeśli się mylę, a później zmodyfikuję swój post), istnieją dwa sposoby korzystania z bibliotek podczas tworzenia aplikacji:

  1. Biblioteki statyczne (pliki .a): W czasie łączenia kopia całej biblioteki jest umieszczana w końcowej aplikacji, dzięki czemu funkcje w bibliotece są zawsze dostępne dla aplikacji wywołującej
  2. Obiekty współdzielone (pliki .so): W czasie łączenia obiekt jest właśnie weryfikowany pod kątem interfejsu API za pomocą odpowiedniego pliku nagłówka (.h). Biblioteka nie jest używana do czasu uruchomienia, gdy jest potrzebna.

Oczywistą zaletą bibliotek statycznych jest to, że pozwalają one na samodzielne zamknięcie całej aplikacji, natomiast zaletą bibliotek dynamicznych jest to, że plik „.so” można zastąpić (tj. W przypadku konieczności aktualizacji ze względu na bezpieczeństwo błąd) bez konieczności ponownej kompilacji aplikacji podstawowej.

Słyszałem, że niektórzy ludzie rozróżniają obiekty współdzielone od dynamicznie połączonych bibliotek (DLL), mimo że oba są plikami „.so”. Czy istnieje rozróżnienie między obiektami współdzielonymi a bibliotekami DLL, jeśli chodzi o tworzenie C / C ++ w systemie Linux lub innym systemie operacyjnym zgodnym z POSIX (tj. MINIX, UNIX, QNX itp.)? Powiedziano mi, że jedną kluczową różnicą (jak dotąd) jest to, że obiekty współdzielone są właśnie używane w czasie wykonywania, podczas gdy biblioteki DLL muszą być najpierw otwarte za pomocą wywołania dlopen () w aplikacji.

Na koniec słyszałem też, jak niektórzy programiści wspominali o „udostępnianych archiwach”, które, moim zdaniem, są również bibliotekami statycznymi, ale nigdy nie są używane przez aplikację bezpośrednio. Zamiast tego inne biblioteki statyczne połączą się z „współdzielonymi archiwami”, aby pobrać niektóre (ale nie wszystkie) funkcje / zasoby ze współdzielonego archiwum do budowanej biblioteki statycznej.

Z góry dziękuję wszystkim za pomoc.

Aktualizacja


W kontekście, w którym te warunki zostały mi przekazane, były to faktycznie błędne terminy używane przez zespół programistów Windows, którzy musieli nauczyć się Linuksa. Próbowałem je poprawić, ale (nieprawidłowe) normy językowe utknęły.

  1. Obiekt współdzielony: biblioteka, która jest automatycznie łączona z programem podczas uruchamiania programu i istnieje jako samodzielny plik. Biblioteka jest dołączana do listy linków w czasie kompilacji (tj .: LDOPTS+=-lmylibdla pliku biblioteki o nazwie mylib.so). Biblioteka musi być obecna w czasie kompilacji i podczas uruchamiania aplikacji.
  2. Biblioteka statyczna: biblioteka, która jest scalana z samym programem w czasie kompilacji dla jednej (większej) aplikacji zawierającej kod aplikacji i kod biblioteki, który jest automatycznie łączony z programem podczas budowania programu, oraz końcowy plik binarny zawierający oba program główny i sama biblioteka istnieje jako pojedynczy autonomiczny plik binarny. Biblioteka jest dołączana do listy linków w czasie kompilacji (np. LDOPTS+=-lmylibDla pliku biblioteki o nazwie mylib.a). Biblioteka musi być obecna w czasie kompilacji.
  3. DLL: Zasadniczo taki sam jak obiekt współdzielony, ale zamiast znajdować się na liście linków w czasie kompilacji, biblioteka jest ładowana za pomocą komend dlopen()/ dlsym(), dzięki czemu biblioteka nie musi być obecna w czasie kompilacji, aby program mógł się skompilować. Ponadto biblioteka nie musi być obecna (koniecznie) podczas uruchamiania aplikacji lub podczas kompilacji , ponieważ jest potrzebna tylko w momencie wywołania dlopen/ dlsym.
  4. Shared Archive: Zasadniczo taki sam jak biblioteka statyczna, ale jest skompilowany z flagami „export-shared” i „-fPIC”. Biblioteka jest dołączana do listy linków w czasie kompilacji (tj .: LDOPTS+=-lmylibSdla pliku biblioteki o nazwie mylibS.a). Różnica między nimi polega na tym, że ta dodatkowa flaga jest wymagana, jeśli obiekt współdzielony lub biblioteka DLL chce statycznie połączyć udostępnione archiwum ze swoim własnym kodem ORAZ móc udostępnić funkcje w obiekcie współdzielonym innym programom, a nie tylko ich używać wewnętrzny do DLL. Jest to przydatne w przypadku, gdy ktoś udostępnia ci bibliotekę statyczną i chcesz ją przepakować jako SO. Biblioteka musi być obecna w czasie kompilacji.

Dodatkowa aktualizacja

Różnica między „ DLL” a „ shared library” była po prostu (leniwym, niedokładnym) kolokwializmem w firmie, w której pracowałem w tym czasie (programiści Windows są zmuszeni przejść na rozwój Linuksa, a termin utknął), zgodnie z opisanymi powyżej opisami.

Dodatkowo, Sliterał „ ” po nazwie biblioteki, w przypadku „współdzielonych archiwów” był tylko konwencją stosowaną w tej firmie, a nie w całej branży.


14
W przypadku .aplików „a” oznacza „archove” i jest to po prostu archiwum plików obiektowych. Nowoczesne konsolidatory powinny być na tyle dobre, aby nie trzeba było dołączać biblioteki while, tylko plików obiektów w archiwum, które są potrzebne, a nawet mogą po prostu wykorzystywać sekcje kodu / danych w plikach obiektów, do których się odwołuje.
Jakiś programista koleś

4
DLL to tylko terminologia Windows. Nie stosuje się go na jednorożcach.
R .. GitHub ZATRZYMAJ LÓD



2
@DevNull „arch i ve” oczywiście. :)
Jakiś programista koleś

Odpowiedzi:


93

Zawsze uważałem, że biblioteki DLL i obiekty współdzielone są po prostu różnymi terminami na tę samą rzecz - Windows nazywa je bibliotekami DLL, podczas gdy w systemach UNIX są to obiekty współdzielone, a ogólny termin - dynamicznie połączona biblioteka - obejmuje oba te elementy (nawet funkcję otwarcie .so w systemie UNIX jest nazywane dlopen()po „bibliotece dynamicznej”).

Są one rzeczywiście połączone tylko podczas uruchamiania aplikacji, jednak twoje pojęcie weryfikacji względem pliku nagłówkowego jest nieprawidłowe. Plik nagłówkowy definiuje prototypy, które są wymagane w celu skompilowania kodu korzystającego z biblioteki, ale w czasie łączenia linker zagląda do samej biblioteki, aby upewnić się, że potrzebne jej funkcje faktycznie istnieją. Linker musi znaleźć ciała funkcji gdzieś w czasie łączenia, w przeciwnym razie zgłosi błąd. Robi to TAKŻE w czasie wykonywania, ponieważ, jak słusznie zauważyłeś, sama biblioteka mogła ulec zmianie od czasu kompilacji programu. Dlatego stabilność ABI jest tak ważna w bibliotekach platform, ponieważ zmiana ABI jest tym, co psuje istniejące programy skompilowane ze starszymi wersjami.

Biblioteki statyczne to tylko pakiety plików obiektowych prosto z kompilatora, podobnie jak te, które budujesz jako część kompilacji projektu, więc są one pobierane i przekazywane do linkera w ten sam sposób, a nieużywane bity są spadł dokładnie w ten sam sposób.


1
Dlaczego niektóre projekty, które widzę w systemie Linux, muszą używać wywołania dlopen (), aby uzyskać dostęp do funkcji w pliku „.so”, a niektóre wcale tego nie robią? Nawiasem mówiąc, dziękuję!
Chmura,

9
Ci, którzy tego nie robią, otrzymują funkcje przekazane im przez moduł ładujący proces, tj. Moduł ładujący elf systemu Linux. dlopen istnieje, jeśli aplikacja chce się otworzyć i użyć pliku .so lub .dll, którego nie było podczas kompilacji lub po prostu dodać dodatkową funkcjonalność, taką jak wtyczki.
rapadura

Ale czy aplikacja w ogóle się nie skompiluje, jeśli .so nie będzie obecny podczas kompilacji? Czy można zmusić linker do zbudowania końcowego programu bez .so w ogóle? Dziękuję Ci.
Chmura

1
Wierzę, że to zależy od tego, jak korzystasz z funkcji z .so, ale tutaj moja wiedza o tym zatrzymuje się: / Dobre pytania.
rapadura

1
Jeśli chodzi o dlopen () i jego rodzinę funkcji, rozumiem, że służy to do programowego otwierania / zamykania biblioteki dll, aby nie musiała być ładowana do pamięci przez cały czas działania aplikacji. W przeciwnym razie musisz powiedzieć linkerowi w argumentach wiersza poleceń (inaczej twój makefile), że chcesz załadować bibliotekę. Zostanie załadowany w czasie wykonywania i pozostanie załadowany w pamięci do momentu zamknięcia aplikacji. Jest więcej rzeczy, które mogą się zdarzyć na poziomie systemu operacyjnego, ale mniej więcej tak dzieje się w przypadku twojej aplikacji.
Taylor Price

197

Biblioteki statyczne (.a) jest biblioteki, które mogą być połączone bezpośrednio do końcowego wykonywalnego wytwarzanego przez łącznik, jest on zawarty w nim i nie ma potrzeby, aby biblioteki do systemu, w którym wykonywalny rozmieszczenia.

Dzielone biblioteki (.so) to biblioteka, która jest związana, ale nie osadzone w końcowym wykonywalnego, więc będzie ładowany, gdy plik wykonywalny jest uruchomiony i muszą być obecne w systemie, w którym wykonywalny jest wdrożony.

Link dynamiczne biblioteki na oknach (.dll) jest jak udostępnionej biblioteki (.so) na linux, ale istnieją pewne różnice między tymi dwoma implementacje, które są związane z OS (system Windows vs Linux):

DLL może zdefiniować dwa rodzaje funkcji: wyeksportowane i wewnętrzny. Wyeksportowane funkcje mają być wywoływane przez inne moduły, a także z biblioteki DLL, w której są zdefiniowane. Funkcje wewnętrzne zazwyczaj są wywoływane tylko z poziomu biblioteki DLL, w której są zdefiniowane.

SO biblioteki na Linux nie wymaga specjalnego oświadczenia eksportowej wskazać symbole wywóz, ponieważ wszystkie symbole są dostępne dla procesu oględzinom.


1
+1 ładne proste wyjaśnienie. Jeśli funkcja jest zadeklarowana jako „wewnętrzna” w bibliotece DLL, czy to oznacza, że nie można jej wywołać spoza biblioteki?
Mike

23
Niekoniecznie jest prawdą, że wszystkie symbole są dostępne w bibliotece SO. Ukryte symbole są możliwe i zalecane, ponieważ użytkownicy bibliotek nie mają powodu, aby zobaczyć wszystkie twoje symbole.
Zan Lynx,

3
FYI: g ++ ma __attribute__składnię dla selektywnie „eksportowanych” symboli:#define DLLEXPORT __attribute__ ((visibility("default"))) #define DLLLOCAL __attribute__ ((visibility("hidden")))
Brian Haak

33

Mogę rozwinąć szczegóły dotyczące bibliotek DLL w systemie Windows, aby pomóc wyjaśnić te tajemnice moim znajomym tutaj w * NIX-land ...

DLL jest jak plik współdzielonego obiektu. Oba są obrazami, gotowymi do załadowania do pamięci przez program ładujący odpowiedni system operacyjny. Obrazom towarzyszą różne bity metadanych, które pomagają linkerom i modułom ładującym w tworzeniu niezbędnych powiązań i korzystaniu z biblioteki kodu.

Biblioteki DLL systemu Windows mają tabelę eksportu. Eksport może odbywać się według nazwy lub według pozycji tabeli (numerycznie). Ta ostatnia metoda jest uważana za „starą szkołę” i jest znacznie bardziej delikatna - przebudowa biblioteki DLL i zmiana pozycji funkcji w tabeli zakończy się katastrofą, podczas gdy nie ma prawdziwego problemu, jeśli łączenie punktów wejścia jest po nazwie. Więc zapomnij o tym jako problem, ale pamiętaj, że istnieje, jeśli pracujesz z kodem „dinozaura”, takim jak biblioteki innych dostawców.

Biblioteki DLL systemu Windows są budowane przez kompilację i łączenie, tak jak w przypadku pliku EXE (aplikacji wykonywalnej), ale biblioteka DLL nie powinna działać samodzielnie, podobnie jak aplikacja SO może być używana przez aplikację poprzez dynamiczne ładowanie lub przez powiązanie czasu połączenia (odwołanie do SO jest osadzone w metadanych pliku binarnego aplikacji, a program ładujący program systemu operacyjnego automatycznie załaduje odwołania SO). Biblioteki DLL mogą odwoływać się do innych bibliotek DLL, podobnie jak SO mogą odnosić się do innych SO.

W systemie Windows biblioteki DLL udostępniają tylko określone punkty wejścia. Są to tak zwane „eksport”. Deweloper może albo użyć specjalnego słowa kluczowego kompilatora, aby uczynić symbol widocznym zewnętrznie (dla innych konsolidatorów i dynamicznego modułu ładującego), lub eksportować można wymienić w pliku definicji modułu, który jest używany w czasie łączenia, gdy sama biblioteka DLL jest powstanie. Współczesna praktyka polega na dekorowaniu definicji funkcji słowem kluczowym w celu wyeksportowania nazwy symbolu. Możliwe jest również tworzenie plików nagłówkowych ze słowami kluczowymi, które zadeklarują ten symbol jako jeden do zaimportowania z biblioteki DLL poza bieżącą jednostką kompilacji. Wyszukaj słowa kluczowe __declspec (dllexport) i __declspec (dllimport), aby uzyskać więcej informacji.

Jedną z interesujących cech bibliotek DLL jest to, że mogą zadeklarować standardową funkcję modułu obsługi „po załadowaniu / rozładowaniu”. Za każdym razem, gdy biblioteka DLL jest ładowana lub rozładowywana, biblioteka DLL może, w zależności od przypadku, przeprowadzić pewne inicjowanie lub czyszczenie. To ładnie odwzorowuje posiadanie DLL jako obiektowego menedżera zasobów, takiego jak sterownik urządzenia lub interfejs współdzielonych obiektów.

Gdy programista chce użyć już wbudowanej biblioteki DLL, musi albo odwołać się do „biblioteki eksportu” (* .LIB) utworzonej przez programistę DLL podczas tworzenia biblioteki DLL, albo musi jawnie załadować bibliotekę DLL w czasie wykonywania i zażądać adres punktu wejścia według nazwy za pośrednictwem mechanizmów LoadLibrary () i GetProcAddress (). W większości przypadków łączenie z plikiem LIB (który po prostu zawiera metadane linkera dla eksportowanych punktów wejścia DLL) jest sposobem, w jaki biblioteki DLL są wykorzystywane. Ładowanie dynamiczne jest zwykle zarezerwowane do implementacji „polimorfizmu” lub „konfigurowalności środowiska wykonawczego” w zachowaniach programu (dostęp do dodatków lub późniejszej funkcjonalności, czyli „wtyczek”).

Sposób działania systemu Windows może czasami powodować pewne zamieszanie; system korzysta z rozszerzenia .LIB w odniesieniu zarówno do normalnych bibliotek statycznych (archiwów, takich jak pliki POSIX * .a), jak i do bibliotek „eksportu kodów pośrednich” potrzebnych do powiązania aplikacji z biblioteką DLL w czasie łączenia. Dlatego zawsze należy sprawdzić, czy plik * .LIB ma taki sam plik * .DLL; jeśli nie, istnieje duże prawdopodobieństwo, że plik * .LIB jest statycznym archiwum biblioteki, a nie eksportuje metadanych wiązania dla biblioteki DLL.


4

Masz rację, że pliki statyczne są kopiowane do aplikacji w czasie łączenia, a udostępnione pliki są po prostu weryfikowane w czasie łączenia i ładowane w czasie wykonywania.

Wywołanie dlopen dotyczy nie tylko obiektów współdzielonych, jeśli aplikacja chce to zrobić w czasie wykonywania w swoim imieniu, w przeciwnym razie obiekty współdzielone są ładowane automatycznie podczas uruchamiania aplikacji. DLLS i .so są tym samym. dlopen istnieje, aby dodać jeszcze bardziej drobnoziarniste możliwości dynamicznego ładowania procesów. Nie musisz sam używać dlopen do otwierania / używania bibliotek DLL, co dzieje się również podczas uruchamiania aplikacji.


Jaki byłby jeden przykład użycia dlopen () do większej kontroli ładowania? Jeśli SO / DLL jest ładowany automatycznie podczas uruchamiania, to czy dlopen () zamyka go i otwiera ponownie, na przykład z różnymi uprawnieniami lub ograniczeniami? Dziękuję Ci.
Chmura,

1
Uważam, że dlopen dotyczy wtyczek lub podobnych funkcji. Zezwolenia / ograniczenia powinny być takie same, jak w przypadku automatycznego ładowania, a w każdym razie dlopen będzie rekurencyjnie ładował biblioteki zależne.
rapadura

DLL i nie.sodokładnie tym samym. Zobacz tę odpowiedź
Basile Starynkevitch,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.