Jak działa biblioteka importu? Detale?

88

Wiem, że to może wydawać się dość proste dla maniaków. Ale chcę, żeby było to krystalicznie jasne.

Kiedy chcę użyć biblioteki DLL Win32, zwykle po prostu wywołuję interfejsy API, takie jak LoadLibrary () i GetProcAdderss (). Ale ostatnio rozwijam się z DirectX9 i muszę dodać pliki d3d9.lib , d3dx9.lib itp.

Słyszałem wystarczająco dużo, że LIB służy do łączenia statycznego, a DLL do łączenia dynamicznego.

Dlatego obecnie rozumiem, że LIB zawiera implementację metod i jest statycznie łączony w czasie łączenia jako część końcowego pliku EXE. Biblioteka DLL jest dynamicznie ładowana w czasie wykonywania i nie jest częścią końcowego pliku EXE.

Ale czasami z plikami DLL są dostarczane pliki LIB , więc:

  • Do czego służą te pliki LIB?
  • Jak osiągają to, do czego są przeznaczone?
  • Czy są jakieś narzędzia, które pozwolą mi sprawdzić wewnętrzne elementy tych plików LIB?

Zaktualizuj 1

Po sprawdzeniu wikipedii pamiętam, że te pliki LIB nazywają się biblioteką importu . Ale zastanawiam się, jak działa dynamiczne ładowanie mojej głównej aplikacji i bibliotek DLL.

Zaktualizuj 2

Tak jak powiedział RBerteig, w plikach LIB utworzonych z bibliotekami DLL znajduje się kod pośredniczący. Więc sekwencja wywołania powinna wyglądać następująco:

Moja główna aplikacja -> odgałęzienie w LIB -> prawdziwa docelowa biblioteka DLL

Więc jakie informacje powinny być zawarte w tych LIB? Mogę pomyśleć o następujących rzeczach:

  • Plik LIB powinien zawierać pełną ścieżkę odpowiedniej biblioteki DLL; Zatem biblioteka DLL może zostać załadowana przez środowisko wykonawcze.
  • Względny adres (lub przesunięcie pliku?) Punktu wejścia każdej metody eksportu DLL powinien być zakodowany w kodowaniu; Więc można było wykonać poprawne skoki / wywołania metod.

Czy mam rację? Czy jest coś więcej?

BTW: Czy jest jakieś narzędzie, które może sprawdzić bibliotekę importu? Jeśli to zobaczę, nie będzie więcej wątpliwości.

smwikipedia
źródło
4
Widzę, że nikt nie odpowiedział na ostatnią część twojego pytania, która dotyczy narzędzi, które mogą przeglądać bibliotekę importu. W przypadku języka Visual C ++ można to zrobić na co najmniej dwa sposoby: lib /list xxx.libi link /dump /linkermember xxx.lib. Zobacz to pytanie o przepełnienie stosu .
Alan
Ponadto, dumpbin -headers xxx.libzapewnia bardziej szczegółowe informacje, w porównaniu do libi linkużyteczności publicznej.
m_katsifarakis

Odpowiedzi:

102

Łączenie z plikiem DLL może nastąpić niejawnie w czasie łączenia kompilacji lub jawnie w czasie wykonywania. Tak czy inaczej, biblioteka DLL zostaje załadowana do przestrzeni pamięci procesów, a wszystkie jej wyeksportowane punkty wejścia są dostępne dla aplikacji.

Jeśli jest używany jawnie w czasie wykonywania, możesz użyć LoadLibrary()i GetProcAddress()ręcznie załadować bibliotekę DLL i uzyskać wskaźniki do funkcji, które musisz wywołać.

Jeśli program jest połączony niejawnie podczas budowania programu, wówczas kody pośredniczące dla każdego eksportu DLL używanego przez program są łączone z programem z biblioteki importu, a te kody pośredniczące są aktualizowane, gdy pliki EXE i DLL są ładowane po uruchomieniu procesu. (Tak, tutaj uprościłem więcej niż trochę ...)

Te kody pośredniczące muszą skądś pochodzić, aw łańcuchu narzędzi firmy Microsoft pochodzą ze specjalnej formy pliku .LIB zwanej biblioteką importu . Wymagana biblioteka .LIB jest zwykle budowana w tym samym czasie co biblioteka DLL i zawiera kod pośredniczący dla każdej funkcji eksportowanej z biblioteki DLL.

Co dziwne, statyczna wersja tej samej biblioteki byłaby również dostarczana jako plik .LIB. Nie ma prostego sposobu na ich rozróżnienie, z wyjątkiem tego, że biblioteki LIB, które są bibliotekami importowanymi dla bibliotek DLL, będą zwykle mniejsze (często znacznie mniejsze) niż pasująca statyczna biblioteka LIB.

Jeśli używasz zestawu narzędzi GCC, nawiasem mówiąc, nie potrzebujesz bibliotek importu, aby pasowały do ​​twoich bibliotek DLL. Wersja linkera Gnu przeportowana do Windows bezpośrednio rozumie biblioteki DLL i może zsyntetyzować większość wymaganych kodów pośredniczących w locie.

Aktualizacja

Jeśli po prostu nie możesz się oprzeć wiedzy, gdzie naprawdę są wszystkie śruby i nakrętki i co się naprawdę dzieje, w MSDN zawsze jest coś, co może Ci pomóc. Artykuł Matta Pietreka Dogłębne spojrzenie na format przenośnego pliku wykonywalnego Win32 to bardzo kompletny przegląd formatu pliku EXE oraz tego, jak jest ładowany i uruchamiany. Został nawet zaktualizowany, aby objąć .NET i więcej, odkąd pojawił się w MSDN Magazine ca. 2002.

Pomocne może być również poznanie, jak dokładnie dowiedzieć się, jakie biblioteki DLL są używane przez program. Narzędziem służącym do tego jest Dependency Walker, znany również jako depend.exe. Jego wersja jest dołączona do programu Visual Studio, ale najnowsza wersja jest dostępna u jej autora pod adresem http://www.dependencywalker.com/ . Potrafi zidentyfikować wszystkie biblioteki DLL, które zostały określone w czasie połączenia (zarówno wczesne ładowanie, jak i ładowanie z opóźnieniem), a także może uruchomić program i obserwować wszelkie dodatkowe biblioteki DLL, które ładuje w czasie wykonywania.

Zaktualizuj 2

Przeformułowałem niektóre z wcześniejszych tekstów, aby wyjaśnić go przy ponownym czytaniu i aby użyć terminów sztuki ukrytych i jawnych w celu zachowania spójności z MSDN.

Mamy więc trzy sposoby udostępnienia funkcji bibliotecznych do użycia przez program. Oczywiste pytanie uzupełniające brzmi zatem: „Jak wybrać drogę?”

Łączenie statyczne polega na łączeniu większości samego programu. Wszystkie pliki obiektowe są wyświetlane na liście i są gromadzone razem w pliku EXE przez konsolidator. Po drodze linker zajmuje się drobnymi obowiązkami, takimi jak naprawianie odwołań do symboli globalnych, aby moduły mogły wywoływać nawzajem funkcje. Biblioteki można również łączyć statycznie. Pliki obiektowe, które tworzą bibliotekę, są gromadzone razem przez bibliotekarza w pliku .LIB, który konsolidator wyszukuje moduły zawierające potrzebne symbole. Jednym ze skutków łączenia statycznego jest to, że tylko te moduły z biblioteki, które są używane przez program, są z nią połączone; inne moduły są ignorowane. Na przykład tradycyjna biblioteka matematyczna C zawiera wiele funkcji trygonometrycznych. Ale jeśli połączysz się z nim i użyjeszcos(), nie otrzymasz kopii kodu dla sin()lub o tan()ile nie wywołałeś również tych funkcji. W przypadku dużych bibliotek z bogatym zestawem funkcji ważne jest to selektywne włączanie modułów. Na wielu platformach, takich jak systemy wbudowane, całkowity rozmiar kodu dostępnego do wykorzystania w bibliotece może być duży w porównaniu z przestrzenią dostępną do przechowywania pliku wykonywalnego w urządzeniu. Bez selektywnego włączania trudniej byłoby zarządzać szczegółami tworzenia programów dla tych platform.

Jednak posiadanie kopii tej samej biblioteki w każdym uruchomionym programie stanowi obciążenie dla systemu, który zwykle wykonuje wiele procesów. Przy odpowiednim systemie pamięci wirtualnej strony pamięci o identycznej zawartości muszą istnieć tylko raz w systemie, ale mogą być używane przez wiele procesów. Stwarza to korzyść w postaci zwiększenia prawdopodobieństwa, że ​​strony zawierające kod będą prawdopodobnie identyczne z niektórymi stronami w tak wielu innych uruchomionych procesach, jak to tylko możliwe. Ale jeśli programy statycznie łączą się z biblioteką wykonawczą, to każdy z nich ma inną kombinację funkcji, z których każda jest rozmieszczona w celu przetwarzania mapy pamięci w różnych lokalizacjach i nie ma wielu współdzielonych stron kodowych, chyba że jest to program, który sam w sobie jest działać w czymś więcej niż tylko proces. Tak więc pomysł biblioteki DLL zyskał kolejną, główną zaletę.

Biblioteka DLL dla biblioteki zawiera wszystkie jej funkcje, gotowe do użycia przez dowolny program kliencki. Jeśli wiele programów ładuje tę bibliotekę DLL, wszystkie mogą udostępniać jej strony kodowe. Wszyscy wygrywają. (Cóż, dopóki nie zaktualizujesz biblioteki DLL nową wersją, ale to nie jest część tej historii. Google DLL Piekło po tej stronie historii).

Tak więc pierwszy duży wybór podczas planowania nowego projektu dotyczy połączenia dynamicznego i statycznego. Dzięki statycznemu łączeniu masz mniej plików do zainstalowania i jesteś odporny na aktualizację używanej biblioteki DLL przez osoby trzecie. Jednak twój program jest większy i nie jest tak dobrym obywatelem ekosystemu Windows. Dzięki dynamicznemu łączeniu masz więcej plików do zainstalowania, możesz mieć problemy z aktualizacją używanej biblioteki DLL przez inną firmę, ale generalnie jesteś bardziej przyjazny dla innych procesów w systemie.

Dużą zaletą biblioteki DLL jest to, że można ją załadować i używać bez ponownej kompilacji lub nawet ponownego łączenia głównego programu. Może to pozwolić zewnętrznemu dostawcy biblioteki (na przykład Microsoftowi i środowisku wykonawczemu C) naprawić błąd w swojej bibliotece i rozpowszechnić go. Gdy użytkownik końcowy zainstaluje zaktualizowaną bibliotekę DLL, natychmiast skorzysta z tej poprawki błędu we wszystkich programach, które używają tej biblioteki DLL. (Chyba że coś zepsuje. Zobacz DLL Hell).

Druga zaleta wynika z rozróżnienia między niejawnym i jawnym ładowaniem. Jeśli podejmiesz dodatkowy wysiłek w postaci jawnego ładowania, biblioteka DLL może nawet nie istnieć, gdy program został napisany i opublikowany. Pozwala to na mechanizmy rozszerzeń, które mogą na przykład wykrywać i ładować wtyczki.

RBerteig
źródło
3
Usunięcie mojego posta i zagłosowanie na to, ponieważ wyjaśniasz wszystko o wiele lepiej niż ja;) Dobra odpowiedź.
ereOn
2
@RBerteig: Dziękuję za wspaniałą odpowiedź. Tylko mała korekta, zgodnie z tutaj ( msdn.microsoft.com/en-us/library/9yd93633.aspx ), istnieją 2 typy dynamicznego łączenia z biblioteką DLL, niejawne łączenie w czasie ładowania i jawne łączenie w czasie wykonywania . Brak łączenia w czasie kompilacji . Teraz zastanawiam się, jaka jest różnica między tradycyjnym łączeniem statycznym (łączeniem do pliku * .lib, który zawiera pełną implementację) a dynamicznym łączeniem w czasie ładowania z biblioteką DLL (za pośrednictwem biblioteki importu)?
smwikipedia
1
Kontynuuj: Jakie są zalety i wady łączenia statycznego i dynamicznego łączenia w czasie ładowania ? Wygląda na to, że oba te podejścia ładują wszystkie niezbędne pliki do przestrzeni adresowej na początku procesu. Dlaczego potrzebujemy 2 z nich? Dzięki.
smwikipedia
1
możesz użyć narzędzia takiego jak „objdump”, aby zajrzeć do pliku .lib i dowiedzieć się, czy jest to biblioteka importu, czy prawdziwa biblioteka statyczna. w systemie Linux podczas kompilacji krzyżowej do miejsca docelowego systemu Windows można uruchomić „ar” lub „nm” na plikach .a (wersja mingw plików .lib) i zauważyć, że biblioteki importu mają ogólne nazwy plików .o i nie zawierają kodu (tylko instrukcja „jmp”), podczas gdy biblioteki statyczne zawierają wiele funkcji i kodu.
don jasne
1
Mała poprawka: możesz również niejawnie łączyć się w czasie wykonywania. Obsługa konsolidatora dla bibliotek DLL ładowanych z opóźnieniem wyjaśnia to szczegółowo. Jest to przydatne, jeśli chcesz dynamicznie zmieniać ścieżkę wyszukiwania DLL lub z wdziękiem obsługiwać błąd rozpoznawania importu (aby obsługiwać nowe funkcje systemu operacyjnego, ale nadal działać na przykład w starszych wersjach).
Niespodziewane
5

Te pliki bibliotek importu .LIB są używane w poniższej właściwości projektu Linker->Input->Additional Dependencies, podczas budowania zestawu bibliotek dll, które wymagają dodatkowych informacji w czasie łączenia, które są dostarczane przez pliki .LIB biblioteki importu. W poniższym przykładzie, aby nie uzyskać błędów konsolidatora, muszę odwoływać się do bibliotek dll A, B, C i D za pośrednictwem ich plików lib. (uwaga dla konsolidatora, aby znaleźć te pliki, może być konieczne dołączenie ich ścieżki wdrażania, w przeciwnym Linker->General->Additional Library Directoriesrazie zostanie wyświetlony błąd kompilacji dotyczący niemożności znalezienia żadnego z dostarczonych plików lib).

Linker-> Input-> Additional Dependencies

Jeśli Twoje rozwiązanie buduje wszystkie biblioteki dynamiczne, być może udało Ci się uniknąć tej jawnej specyfikacji zależności, polegając zamiast tego na flagach referencyjnych ujawnionych w Common Properties->Framework and Referencesoknie dialogowym. Te flagi wydają się automatycznie tworzyć łącza w Twoim imieniu przy użyciu plików * .lib. Ramy i referencje

Jest to jednak tak, jak mówi, Właściwości wspólne , które nie są specyficzne dla konfiguracji ani platformy. Jeśli potrzebujesz obsługiwać scenariusz kompilacji mieszanej, tak jak w naszej aplikacji, mieliśmy konfigurację kompilacji do renderowania kompilacji statycznej i specjalną konfigurację, która zbudowała ograniczoną kompilację podzbioru zestawów, które zostały wdrożone jako biblioteki dynamiczne. Użyłem flag Use Library Dependency Inputs i Link Library Dependenciesustawionych na true w różnych przypadkach, aby uzyskać rzeczy do zbudowania, a później uświadomiłem sobie, aby uprościć rzeczy, ale wprowadzając mój kod do kompilacji statycznych, wprowadziłem mnóstwo ostrzeżeń linkera, a kompilacja była niesamowicie powolna w przypadku kompilacji statycznych. Skończyło się na wprowadzeniu kilku tego rodzaju ostrzeżeń ...

warning LNK4006: "bool __cdecl XXX::YYY() already defined in CoreLibrary.lib(JSource.obj); second definition ignored  D.lib(JSource.obj)

Skończyło się na tym, że użyłem ręcznej specyfikacji programu, Additional Dependenciesaby zadowolić konsolidator dla dynamicznych kompilacji, a jednocześnie zachować zadowolenie statycznych budowniczych, nie używając wspólnej właściwości, która je spowalnia. Kiedy wdrażam kompilację podzbioru dynamicznego, wdrażam tylko pliki dll, ponieważ te pliki lib są używane tylko w czasie łączenia, a nie w czasie wykonywania.

jxramos
źródło
3

Istnieją trzy rodzaje bibliotek: biblioteki statyczne, współdzielone i ładowane dynamicznie.

Biblioteki statyczne są połączone z kodem na etapie łączenia, więc w rzeczywistości znajdują się w pliku wykonywalnym, w przeciwieństwie do biblioteki współdzielonej, która ma tylko kody pośredniczące (symbole) do wyszukania w pliku biblioteki współdzielonej, który jest ładowany w czasie wykonywania przed wywoływana jest funkcja główna.

Te ładowane dynamicznie są bardzo podobne do bibliotek współdzielonych, z wyjątkiem tego, że są ładowane, gdy i jeśli taka potrzeba wynika z napisanego kodu.

Zoltán Szőcs
źródło
@ Dzięki zacsek. Ale nie jestem pewien co do twojego oświadczenia o udostępnionej bibliotece.
smwikipedia
@smwikipedia: Linux je ma, używam ich, więc z całą pewnością istnieją. Przeczytaj także: en.wikipedia.org/wiki/Library_(computing)
Zoltán Szőcs
3
To subtelna różnica. Biblioteki udostępnione i biblioteki dynamiczne są plikami DLL. Różnica polega na tym, kiedy są załadowane. Biblioteki współdzielone są ładowane przez system operacyjny wraz z plikiem EXE. Biblioteki dynamiczne są ładowane przez wywołanie kodu LoadLibrary()i powiązane interfejsy API.
RBerteig
Czytałem z [1], że DLL jest implementacją koncepcji Shared Library firmy Microsoft. [1]: en.wikipedia.org/wiki/Dynamic-link_library#Import_libraries
smwikipedia
Nie zgadzam się, że jest to subtelna różnica, z punktu widzenia programowania robi to ogromną różnicę, czy biblioteka współdzielona jest ładowana dynamicznie, czy nie (jeśli jest ładowana dynamicznie, musisz dodać standardowy kod, aby uzyskać dostęp do funkcji).
Zoltán Szőcs