Wie funktioniert die Importbibliothek? Einzelheiten?

86

Ich weiß, dass dies für Geeks ziemlich einfach erscheinen mag. Aber ich möchte es kristallklar machen.

Wenn ich eine Win32-DLL verwenden möchte, rufe ich normalerweise nur die APIs wie LoadLibrary () und GetProcAdderss () auf. Aber vor kurzem entwickle ich mit DirectX9 und muss Dateien d3d9.lib , d3dx9.lib usw. hinzufügen .

Ich habe genug gehört, dass LIB für statische Verknüpfung und DLL für dynamische Verknüpfung ist.

Mein derzeitiges Verständnis ist also, dass LIB die Implementierung der Methoden enthält und zum Zeitpunkt der Verknüpfung als Teil der endgültigen EXE-Datei statisch verknüpft ist. Während DLL zur Laufzeit dynamisch geladen wird und nicht Teil der endgültigen EXE-Datei ist.

Aber manchmal kommen einige LIB-Dateien mit den DLL-Dateien, also:

  • Wofür sind diese LIB-Dateien gedacht?
  • Wie erreichen sie, wofür sie bestimmt sind?
  • Gibt es Tools, mit denen ich die Interna dieser LIB-Dateien überprüfen kann?

Update 1

Nachdem ich Wikipedia überprüft habe, erinnere ich mich, dass diese LIB-Dateien als Importbibliothek bezeichnet werden . Aber ich frage mich, wie es mit meiner Hauptanwendung und den DLLs funktioniert, dynamisch geladen zu werden.

Update 2

Wie RBerteig sagte, enthält die mit den DLLs geborene LIB-Datei einen Stub-Code. Die Aufrufsequenz sollte also folgendermaßen aussehen:

Meine Hauptanwendung -> Stub in der LIB -> echte Ziel-DLL

Welche Informationen sollten in diesen LIBs enthalten sein? Ich könnte mir Folgendes vorstellen:

  • Die LIB-Datei sollte den vollständigen Pfad der entsprechenden DLL enthalten. Die DLL könnte also zur Laufzeit geladen werden.
  • Die relative Adresse (oder der Dateiversatz?) Des Einstiegspunkts jeder DLL-Exportmethode sollte im Stub codiert werden. So könnten korrekte Sprünge / Methodenaufrufe gemacht werden.

Habe ich recht damit? Gibt es noch etwas?

Übrigens: Gibt es ein Tool, das eine Importbibliothek überprüfen kann? Wenn ich es sehen kann, wird es keine Zweifel mehr geben.

smwikipedia
quelle
4
Ich sehe, dass niemand den letzten Teil Ihrer Frage angesprochen hat, der Tools betrifft, mit denen eine Importbibliothek überprüft werden kann. In Visual C ++ gibt es mindestens zwei Möglichkeiten: lib /list xxx.libund link /dump /linkermember xxx.lib. Siehe diese Frage zum Stapelüberlauf .
Alan

Antworten:

99

Die Verknüpfung mit einer DLL-Datei kann implizit zur Kompilierungsverknüpfungszeit oder explizit zur Laufzeit erfolgen. In beiden Fällen wird die DLL in den Speicher des Prozesses geladen, und alle exportierten Einstiegspunkte stehen der Anwendung zur Verfügung.

Bei expliziter Verwendung zur Laufzeit verwenden Sie LoadLibrary()und GetProcAddress()zum manuellen Laden der DLL und zum Abrufen von Zeigern auf die Funktionen, die Sie aufrufen müssen.

Wenn beim Erstellen des Programms implizit eine Verknüpfung hergestellt wird, werden Stubs für jeden vom Programm verwendeten DLL-Export aus einer Importbibliothek mit dem Programm verknüpft, und diese Stubs werden aktualisiert, wenn EXE und DLL beim Start des Prozesses geladen werden. (Ja, ich habe hier mehr als ein bisschen vereinfacht ...)

Diese Stubs müssen von irgendwoher kommen, und in der Microsoft-Toolkette stammen sie aus einer speziellen Form einer .LIB-Datei, die als Importbibliothek bezeichnet wird . Die erforderliche .LIB wird normalerweise gleichzeitig mit der DLL erstellt und enthält einen Stub für jede aus der DLL exportierte Funktion.

Verwirrenderweise würde eine statische Version derselben Bibliothek auch als .LIB-Datei geliefert. Es gibt keine triviale Möglichkeit, sie voneinander zu unterscheiden, außer dass LIBs, die Importbibliotheken für DLLs sind, normalerweise kleiner (oft viel kleiner) sind als die passende statische LIB.

Wenn Sie die GCC-Toolchain verwenden, benötigen Sie übrigens keine Importbibliotheken, die Ihren DLLs entsprechen. Die auf Windows portierte Version des Gnu-Linkers versteht DLLs direkt und kann die meisten erforderlichen Stubs im laufenden Betrieb synthetisieren.

Aktualisieren

Wenn Sie einfach nicht widerstehen können, zu wissen, wo sich alle Schrauben und Muttern wirklich befinden und was wirklich los ist, gibt es bei MSDN immer etwas zu helfen. Matt Pietreks Artikel Ein detaillierter Blick auf das tragbare ausführbare Win32-Dateiformat bietet einen vollständigen Überblick über das Format der EXE-Datei und darüber, wie sie geladen und ausgeführt wird. Es wurde sogar aktualisiert, um .NET und mehr abzudecken, seit es ursprünglich im MSDN Magazine erschien. 2002.

Es kann auch hilfreich sein zu wissen, wie man genau lernt, welche DLLs von einem Programm verwendet werden. Das Tool dafür ist Dependency Walker, auch bekannt als abhängige.exe. Eine Version davon ist in Visual Studio enthalten, die neueste Version ist jedoch beim Autor unter http://www.dependencywalker.com/ erhältlich . Es kann alle DLLs identifizieren, die zur Verbindungszeit angegeben wurden (sowohl frühes Laden als auch verzögertes Laden), und es kann das Programm ausführen und nach zusätzlichen DLLs suchen, die es zur Laufzeit lädt.

Update 2

Ich habe einige der früheren Texte umformuliert, um sie beim erneuten Lesen zu verdeutlichen und die impliziten und expliziten Verknüpfungen der Kunstbegriffe zu verwenden , um die Konsistenz mit MSDN zu gewährleisten.

Wir haben also drei Möglichkeiten, Bibliotheksfunktionen zur Verwendung durch ein Programm zur Verfügung zu stellen. Die offensichtliche Folgefrage lautet dann: "Wie wähle ich welchen Weg?"

Durch statische Verknüpfung wird der Großteil des Programms selbst verknüpft. Alle Ihre Objektdateien werden aufgelistet und vom Linker in der EXE-Datei zusammengefasst. Unterwegs kümmert sich der Linker um kleinere Aufgaben wie das Korrigieren von Verweisen auf globale Symbole, damit Ihre Module die Funktionen des anderen aufrufen können. Bibliotheken können auch statisch verknüpft werden. Die Objektdateien, aus denen die Bibliothek besteht, werden von einem Bibliothekar in einer .LIB-Datei gesammelt, die der Linker nach Modulen sucht, die die benötigten Symbole enthalten. Ein Effekt der statischen Verknüpfung besteht darin, dass nur die Module aus der Bibliothek verknüpft werden, die vom Programm verwendet werden. andere Module werden ignoriert. Beispielsweise enthält die traditionelle C-Mathematikbibliothek viele Trigonometriefunktionen. Aber wenn Sie dagegen verlinken und verwendencos()Sie erhalten keine Kopie des Codes für sin()oder, es tan()sei denn, Sie haben auch diese Funktionen aufgerufen. Für große Bibliotheken mit zahlreichen Funktionen ist diese selektive Einbeziehung von Modulen wichtig. Auf vielen Plattformen wie eingebetteten Systemen kann die Gesamtgröße des zur Verwendung in der Bibliothek verfügbaren Codes im Vergleich zu dem Speicherplatz, der zum Speichern einer ausführbaren Datei auf dem Gerät verfügbar ist, groß sein. Ohne selektive Einbeziehung wäre es schwieriger, die Details der Erstellung von Programmen für diese Plattformen zu verwalten.

Wenn jedoch in jedem Programm eine Kopie derselben Bibliothek vorhanden ist, wird ein System belastet, auf dem normalerweise viele Prozesse ausgeführt werden. Mit der richtigen Art von virtuellem Speichersystem müssen Speicherseiten mit identischem Inhalt nur einmal im System vorhanden sein, können jedoch von vielen Prozessen verwendet werden. Dies schafft einen Vorteil für die Erhöhung der Wahrscheinlichkeit, dass die Seiten, die Code enthalten, in so vielen anderen laufenden Prozessen wie möglich mit einigen Seiten identisch sind. Wenn Programme jedoch statisch mit der Laufzeitbibliothek verknüpft sind, verfügt jedes Programm über einen anderen Funktionsmix, der die Speicherzuordnung an verschiedenen Speicherorten verarbeitet, und es gibt nicht viele gemeinsam nutzbare Codeseiten, es sei denn, es handelt sich um ein Programm, das für sich allein ist in mehr als prozess laufen. Die Idee einer DLL hat also einen weiteren großen Vorteil gebracht.

Eine DLL für eine Bibliothek enthält alle ihre Funktionen, die von jedem Client-Programm verwendet werden können. Wenn viele Programme diese DLL laden, können sie alle ihre Codepages gemeinsam nutzen. Jeder gewinnt. (Nun, bis Sie eine DLL mit einer neuen Version aktualisieren, aber das ist nicht Teil dieser Geschichte. Google DLL Hell für diese Seite der Geschichte.)

Die erste große Wahl bei der Planung eines neuen Projekts ist also die dynamische und statische Verknüpfung. Mit der statischen Verknüpfung müssen Sie weniger Dateien installieren und sind immun gegen die Aktualisierung einer von Ihnen verwendeten DLL durch Dritte. Ihr Programm ist jedoch größer und nicht ganz so gut wie das Windows-Ökosystem. Mit der dynamischen Verknüpfung müssen Sie mehr Dateien installieren. Möglicherweise haben Sie Probleme mit der Aktualisierung einer von Ihnen verwendeten DLL durch einen Drittanbieter, sind jedoch im Allgemeinen mit anderen Prozessen auf dem System freundlicher.

Ein großer Vorteil einer DLL ist, dass sie geladen und verwendet werden kann, ohne das Hauptprogramm neu zu kompilieren oder sogar neu zu verknüpfen. Auf diese Weise kann ein Drittanbieter (z. B. Microsoft und die C-Laufzeit) einen Fehler in seiner Bibliothek beheben und verteilen. Sobald ein Endbenutzer die aktualisierte DLL installiert hat, profitiert er sofort von dieser Fehlerbehebung in allen Programmen, die diese DLL verwenden. (Es sei denn, es bricht Dinge. Siehe DLL-Hölle.)

Der andere Vorteil ergibt sich aus der Unterscheidung zwischen implizitem und explizitem Laden. Wenn Sie sich die Mühe machen, explizit zu laden, war die DLL möglicherweise noch nicht einmal vorhanden, als das Programm geschrieben und veröffentlicht wurde. Dies ermöglicht Erweiterungsmechanismen, mit denen beispielsweise Plugins erkannt und geladen werden können.

RBerteig
quelle
3
Lösche meinen Beitrag und stimme dem zu, weil du die Dinge viel besser erklärst als ich;) Schöne Antwort.
8:57 Uhr
2
@RBerteig: Danke für deine tolle Antwort. Nur eine kleine Korrektur, laut hier ( msdn.microsoft.com/en-us/library/9yd93633.aspx ) gibt es zwei Arten der dynamischen Verknüpfung mit einer DLL: implizite Ladezeitverknüpfung und explizite Laufzeitverknüpfung . Keine Verknüpfung zur Kompilierungszeit . Jetzt frage ich mich, was der Unterschied zwischen der herkömmlichen statischen Verknüpfung (Verknüpfung mit einer * .lib-Datei, die die vollständige Implementierung enthält) und der dynamischen Verknüpfung mit Ladezeit zu einer DLL (über eine Importbibliothek) ist.
Smwikipedia
1
Weiter: Was sind die Vor- und Nachteile der statischen Verknüpfung und der dynamischen Ladezeitverknüpfung ? Es scheint, dass diese beiden Ansätze beide zu Beginn eines Prozesses alle erforderlichen Dateien in den Adressraum laden. Warum brauchen wir 2 davon? Vielen Dank.
Smwikipedia
1
Sie können möglicherweise ein Tool wie "objdump" verwenden, um einen Blick in eine .lib-Datei zu werfen und herauszufinden, ob es sich um eine Importbibliothek oder eine echte statische Bibliothek handelt. Unter Linux ist es beim Cross-Kompilieren mit einem Windows-Ziel möglich, 'ar' oder 'nm' für die .a-Dateien (mingw-Version von .lib-Dateien) auszuführen und zu beachten, dass die Importbibliotheken generische .o-Dateinamen und keinen Code haben (nur eine 'jmp'-Anweisung), während die statischen Bibliotheken viele Funktionen und Code enthalten.
Don Bright
1
Kleine Korrektur: Sie können auch zur Laufzeit implizit verknüpfen. Die Linker-Unterstützung für verzögert geladene DLLs erläutert dies ausführlich. Dies ist hilfreich, wenn Sie den DLL-Suchpfad dynamisch ändern oder Fehler bei der Importauflösung ordnungsgemäß behandeln möchten (um neue Betriebssystemfunktionen zu unterstützen, die jedoch beispielsweise auf älteren Versionen ausgeführt werden).
Unsichtbarer
5

Diese .LIB-Importbibliotheksdateien werden in der folgenden Projekteigenschaft verwendet Linker->Input->Additional Dependencies, wenn eine Reihe von DLLs erstellt werden, die zum Zeitpunkt der Verknüpfung zusätzliche Informationen benötigen, die von den .LIB-Dateien der Importbibliothek bereitgestellt werden. Im folgenden Beispiel muss ich über die lib-Dateien auf die DLLs A, B, C und D verweisen, um keine Linkerfehler zu erhalten. (Hinweis: Damit der Linker diese Dateien finden kann, müssen Sie möglicherweise ihren Bereitstellungspfad angeben. Linker->General->Additional Library DirectoriesAndernfalls wird ein Buildfehler angezeigt, wenn keine der bereitgestellten lib-Dateien gefunden werden kann.)

Linker-> Eingabe-> Zusätzliche Abhängigkeiten

Wenn Ihre Lösung alle dynamischen Bibliotheken erstellt, konnten Sie diese explizite Abhängigkeitsspezifikation möglicherweise vermeiden, indem Sie sich stattdessen auf die im Common Properties->Framework and ReferencesDialogfeld angezeigten Referenzflags verlassen . Diese Flags scheinen die Verknüpfung in Ihrem Namen mithilfe der * .lib-Dateien automatisch durchzuführen. Framework und Referenzen

Dies ist jedoch eine allgemeine Eigenschaft, die nicht konfigurations- oder plattformspezifisch ist. Wenn Sie ein gemischtes Build-Szenario wie in unserer Anwendung unterstützen müssen, hatten wir eine Build-Konfiguration zum Rendern eines statischen Builds und eine spezielle Konfiguration, die einen eingeschränkten Build einer Teilmenge von Assemblys erstellt, die als dynamische Bibliotheken bereitgestellt wurden. Ich hatte die verwendete Use Library Dependency Inputs und Link Library DependenciesFahnen auf true gesetzt unter verschiedenen Fällen der Dinge zu bekommen , um zu bauen und später die Dinge zu vereinfachen realisieren, aber wenn mein Code zum statischen Einführung baut ich eine Tonne Linker Warnungen eingeführt und die Build war unglaublich langsam für die statische baut. Am Ende habe ich eine Reihe solcher Warnungen eingeführt ...

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

Und ich habe die manuelle Spezifikation von verwendet Additional Dependencies, um den Linker für die dynamischen Builds zufrieden zu stellen und gleichzeitig die statischen Builder zufrieden zu stellen, indem ich keine gemeinsame Eigenschaft verwendet habe, die sie verlangsamt. Wenn ich den Build für dynamische Teilmengen bereitstelle, stelle ich nur die DLL-Dateien bereit, da diese lib-Dateien nur zur Verbindungszeit und nicht zur Laufzeit verwendet werden.

jxramos
quelle
3

Es gibt drei Arten von Bibliotheken: statische, gemeinsam genutzte und dynamisch geladene Bibliotheken.

Die statischen Bibliotheken werden in der Verknüpfungsphase mit dem Code verknüpft, sodass sie sich tatsächlich in der ausführbaren Datei befinden, im Gegensatz zur gemeinsam genutzten Bibliothek, die nur Stubs (Symbole) enthält, nach denen in der gemeinsam genutzten Bibliotheksdatei gesucht werden muss, die zur Laufzeit vor dem geladen wird Hauptfunktion wird aufgerufen.

Die dynamisch geladenen sind den gemeinsam genutzten Bibliotheken sehr ähnlich, außer dass sie geladen werden, wenn und wenn der von Ihnen geschriebene Code dies erfordert.

Zoltán Szőcs
quelle
@ Danke zacsek. Ich bin mir jedoch nicht sicher über Ihre Aussage zur gemeinsam genutzten Bibliothek.
Smwikipedia
@smwikipedia: Linux hat sie, ich benutze sie, also existieren sie definitiv. Lesen Sie auch: en.wikipedia.org/wiki/Library_(computing)
Zoltán Szőcs
3
Es ist ein subtiler Unterschied. Gemeinsame und dynamische Bibliotheken sind beide DLL-Dateien. Der Unterschied besteht darin, wann sie geladen werden. Freigegebene Bibliotheken werden vom Betriebssystem zusammen mit der EXE-Datei geladen. Dynamische Bibliotheken werden durch Codeaufrufe LoadLibrary()und die zugehörigen APIs geladen .
RBerteig
Ich habe aus [1] gelesen, dass DLL die Implementierung des Shared Library-Konzepts durch Microsoft ist. [1]: en.wikipedia.org/wiki/Dynamic-link_library#Import_libraries
smwikipedia
Ich bin nicht der Meinung, dass dies ein subtiler Unterschied ist. Aus Sicht der Programmierung macht es einen großen Unterschied, ob die gemeinsam genutzte Bibliothek dinamisch geladen ist oder nicht (wenn sie dinamisch geladen ist, müssen Sie Boilerplate-Code hinzufügen, um auf Funktionen zugreifen zu können).
Zoltán Szőcs