Łą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.
lib /list xxx.lib
ilink /dump /linkermember xxx.lib
. Zobacz to pytanie o przepełnienie stosu .