Unterschied zwischen gemeinsam genutzten Objekten (.so), statischen Bibliotheken (.a) und DLLs (.so)?

272

Ich war an einigen Debatten über Bibliotheken unter Linux beteiligt und möchte einige Dinge bestätigen.

Nach meinem Verständnis (bitte korrigieren Sie mich, wenn ich falsch liege und ich werde meinen Beitrag später bearbeiten) gibt es zwei Möglichkeiten, Bibliotheken beim Erstellen einer Anwendung zu verwenden:

  1. Statische Bibliotheken (.a-Dateien): Zum Zeitpunkt der Verknüpfung wird eine Kopie der gesamten Bibliothek in die endgültige Anwendung eingefügt, sodass die Funktionen in der Bibliothek der aufrufenden Anwendung immer zur Verfügung stehen
  2. Freigegebene Objekte (.so-Dateien): Zum Zeitpunkt der Verknüpfung wird das Objekt nur über die entsprechende Header-Datei (.h) anhand seiner API überprüft. Die Bibliothek wird erst zur Laufzeit verwendet, wenn sie benötigt wird.

Der offensichtliche Vorteil statischer Bibliotheken besteht darin, dass die gesamte Anwendung in sich geschlossen sein kann, während dynamische Bibliotheken den Vorteil haben, dass die ".so" -Datei ersetzt werden kann (dh, falls sie aus Sicherheitsgründen aktualisiert werden muss Fehler), ohne dass die Basisanwendung neu kompiliert werden muss.

Ich habe gehört, dass einige Leute zwischen gemeinsam genutzten Objekten und dynamisch verknüpften Bibliotheken (DLLs) unterscheiden, obwohl beide ".so" -Dateien sind. Gibt es einen Unterschied zwischen gemeinsam genutzten Objekten und DLLs, wenn es um die C / C ++ - Entwicklung unter Linux oder einem anderen POSIX-kompatiblen Betriebssystem (z. B. MINIX, UNIX, QNX usw.) geht? Mir wurde gesagt, dass ein wesentlicher Unterschied (bis jetzt) ​​darin besteht, dass gemeinsam genutzte Objekte nur zur Laufzeit verwendet werden, während DLLs zuerst mit dem Aufruf dlopen () in der Anwendung geöffnet werden müssen.

Schließlich habe ich auch gehört, dass einige Entwickler "gemeinsam genutzte Archive" erwähnt haben, die meines Wissens auch selbst statische Bibliotheken sind, aber niemals direkt von einer Anwendung verwendet werden. Stattdessen werden andere statische Bibliotheken mit den "gemeinsam genutzten Archiven" verknüpft, um einige (aber nicht alle) Funktionen / Ressourcen aus dem gemeinsam genutzten Archiv in die zu erstellende statische Bibliothek zu ziehen.

Vielen Dank im Voraus für Ihre Hilfe.

Aktualisieren


In dem Kontext, in dem mir diese Begriffe zur Verfügung gestellt wurden, handelte es sich tatsächlich um fehlerhafte Begriffe, die von einem Team von Windows-Entwicklern verwendet wurden, die Linux lernen mussten. Ich habe versucht, sie zu korrigieren, aber die (falschen) Sprachnormen sind geblieben.

  1. Freigegebenes Objekt: Eine Bibliothek, die beim Programmstart automatisch mit einem Programm verknüpft wird und als eigenständige Datei vorhanden ist. Die Bibliothek wird zur Kompilierungszeit in die Verknüpfungsliste aufgenommen (dh LDOPTS+=-lmylibfür eine Bibliotheksdatei mit dem Namen mylib.so). Die Bibliothek muss zur Kompilierungszeit und beim Start der Anwendung vorhanden sein.
  2. Statische Bibliothek: Eine Bibliothek, die beim Erstellen für eine einzelne (größere) Anwendung, die den Anwendungscode und den Bibliothekscode enthält, der beim Erstellen des Programms automatisch mit einem Programm verknüpft wird, und die endgültige Binärdatei, die beide enthält, mit dem eigentlichen Programm selbst zusammengeführt wird Das Hauptprogramm und die Bibliothek selbst existieren als einzelne eigenständige Binärdatei. Die Bibliothek wird zur Kompilierungszeit in die Verknüpfungsliste aufgenommen (dh LDOPTS+=-lmylibfür eine Bibliotheksdatei mit dem Namen mylib.a). Die Bibliothek muss zur Kompilierungszeit vorhanden sein.
  3. DLL: Entspricht im Wesentlichen einem gemeinsam genutzten Objekt, wird jedoch nicht zur Kompilierungszeit in die Verknüpfungsliste aufgenommen, sondern über dlopen()/ dlsym()-Befehle geladen , sodass die Bibliothek zum Zeitpunkt der Kompilierung nicht vorhanden sein muss, damit das Programm kompiliert werden kann. Außerdem muss die Bibliothek beim Start der Anwendung oder beim Kompilieren nicht (unbedingt) vorhanden sein , da sie nur zum Zeitpunkt des Aufrufs von dlopen/ benötigt wird dlsym.
  4. Shared Archive: Entspricht im Wesentlichen einer statischen Bibliothek, wird jedoch mit den Flags "export-shared" und "-fPIC" kompiliert. Die Bibliothek wird zur Kompilierungszeit in die Verknüpfungsliste aufgenommen (dh LDOPTS+=-lmylibSfür eine Bibliotheksdatei mit dem Namen mylibS.a). Der Unterschied zwischen beiden besteht darin, dass dieses zusätzliche Flag erforderlich ist, wenn ein gemeinsam genutztes Objekt oder eine gemeinsam genutzte DLL das gemeinsam genutzte Archiv statisch mit seinem eigenen Code verknüpfen UND die Funktionen im gemeinsam genutzten Objekt anderen Programmen zur Verfügung stellen möchte, anstatt sie nur zu verwenden intern in der DLL. Dies ist nützlich, wenn Ihnen jemand eine statische Bibliothek zur Verfügung stellt und Sie diese als SO neu verpacken möchten. Die Bibliothek muss zur Kompilierungszeit vorhanden sein.

Zusätzliches Update

Die Unterscheidung zwischen " DLL" und " shared library" war nur eine (faule, ungenaue) Umgangssprache in dem Unternehmen, in dem ich zu der Zeit gearbeitet habe (Windows-Entwickler mussten zur Linux-Entwicklung wechseln, und der Begriff blieb hängen), wobei die oben genannten Beschreibungen eingehalten wurden.

Darüber hinaus war das nachgestellte " S" Literal nach dem Bibliotheksnamen im Fall von "gemeinsam genutzten Archiven" nur eine Konvention, die in diesem Unternehmen und nicht in der Branche im Allgemeinen verwendet wurde.

Wolke
quelle
14
Bei .aDateien steht das "a" tatsächlich für "archove" und ist einfach ein Archiv von Objektdateien. Moderne Linker sollten gut genug sein, um nicht die while-Bibliothek, sondern nur die benötigten Objektdateien im Archiv enthalten zu müssen, und möglicherweise sogar nur die Codeabschnitte / Datenabschnitte in den Objektdateien, auf die verwiesen wird.
Einige Programmierer Typ
4
DLL ist nur Windows-Terminologie. Es wird nicht für Unices verwendet.
R .. GitHub STOP HELPING ICE
2
Lesen Sie tldp.org/HOWTO/Program-Library-HOWTO
Basile Starynkevitch
2
@ DevNull "arch i ve" natürlich. :)
Einige Programmierer Typ

Antworten:

93

Ich habe immer gedacht, dass DLLs und gemeinsam genutzte Objekte nur unterschiedliche Begriffe für dasselbe sind - Windows nennt sie DLLs, während sie auf UNIX-Systemen gemeinsam genutzte Objekte sind, wobei der allgemeine Begriff - dynamisch verknüpfte Bibliothek - beide abdeckt (sogar die Funktion dazu) open a .so unter UNIX wird dlopen()nach 'dynamic library' aufgerufen ).

Sie werden zwar nur beim Start der Anwendung verknüpft, Ihre Vorstellung von einer Überprüfung anhand der Header-Datei ist jedoch falsch. Die Header-Datei definiert Prototypen, die erforderlich sind, um den Code zu kompilieren, der die Bibliothek verwendet. Zum Zeitpunkt der Verknüpfung schaut der Linker jedoch in die Bibliothek selbst, um sicherzustellen, dass die benötigten Funktionen tatsächlich vorhanden sind. Der Linker muss die Funktionskörper irgendwo zur Linkzeit finden, sonst wird ein Fehler ausgelöst. Dies geschieht AUCH zur Laufzeit, da sich, wie Sie zu Recht darauf hinweisen, die Bibliothek selbst möglicherweise geändert hat, seit das Programm kompiliert wurde. Aus diesem Grund ist die ABI-Stabilität in Plattformbibliotheken so wichtig, da durch die ABI-Änderung vorhandene Programme beschädigt werden, die mit älteren Versionen kompiliert wurden.

Statische Bibliotheken sind nur Bündel von Objektdateien direkt aus dem Compiler, genau wie diejenigen, die Sie selbst als Teil der Kompilierung Ihres Projekts erstellen. Sie werden also auf genau dieselbe Weise in den Linker gezogen und dem Linker zugeführt, und nicht verwendete Bits sind es auf genau die gleiche Weise fallen gelassen.

Matthew Walton
quelle
1
Warum müssen einige Projekte, die ich unter Linux sehe, den Aufruf dlopen () verwenden, um auf die Funktionen in einer ".so" -Datei zuzugreifen, und andere müssen dies überhaupt nicht tun? Danke dir übrigens!
Wolke
9
Diejenigen, die dies nicht tun, erhalten die Funktionen vom Prozesslader, dh dem Elfenlader von Linux. dlopen ist vorhanden, wenn die Anwendung eine .so oder .dll öffnen und verwenden möchte, die beim Kompilieren nicht vorhanden war, oder einfach zusätzliche Funktionen wie Plugins hinzufügen möchte.
Rapadura
Aber wird die Anwendung nicht kompiliert, wenn die .so-Datei zum Zeitpunkt der Erstellung nicht vorhanden ist? Ist es möglich, den Linker zu zwingen, nur das endgültige Programm zu erstellen, ohne dass die .so überhaupt vorhanden ist? Danke dir.
Wolke
1
Ich würde glauben, dass es davon abhängt, wie Sie die Funktionen von .so verwenden, aber hier stoppt mein Wissen darüber: / Gute Fragen.
Rapadura
1
In Bezug auf dlopen () und seine Funktionsfamilie verstehe ich, dass dies zum programmgesteuerten Öffnen / Schließen einer DLL verwendet wird, damit sie nicht während des gesamten Anwendungslaufs in den Speicher geladen werden muss. Andernfalls müssen Sie dem Linker in seinen Befehlszeilenargumenten (auch bekannt als Ihr Makefile) mitteilen, dass die Bibliothek geladen werden soll. Es wird zur Laufzeit geladen und bleibt im Speicher geladen, bis die Anwendung beendet wird. Es gibt weitere Dinge, die auf Betriebssystemebene passieren können, aber genau das passiert in Bezug auf Ihre Anwendung.
Taylor Price
197

Eine statische Bibliothek (.a) ist eine Bibliothek, die direkt mit der endgültigen ausführbaren Datei verknüpft werden kann, die vom Linker erstellt wurde. Sie ist darin enthalten, und es ist nicht erforderlich, dass die Bibliothek in dem System vorhanden ist, in dem die ausführbare Datei bereitgestellt wird.

Eine gemeinsam genutzte Bibliothek (.so) ist eine Bibliothek, die verknüpft, aber nicht in die endgültige ausführbare Datei eingebettet ist. Sie wird daher beim Start der ausführbaren Datei geladen und muss auf dem System vorhanden sein, auf dem die ausführbare Datei bereitgestellt wird.

Eine dynamische Linkbibliothek unter Windows (.dll) ähnelt einer gemeinsam genutzten Bibliothek (.so) unter Linux, es gibt jedoch einige Unterschiede zwischen den beiden Implementierungen, die sich auf das Betriebssystem beziehen (Windows vs Linux):

Eine DLL kann zwei Arten von Funktionen definieren: exportiert und intern. Die exportierten Funktionen sollen von anderen Modulen sowie aus der DLL heraus aufgerufen werden, in der sie definiert sind. Interne Funktionen sollen normalerweise nur innerhalb der DLL aufgerufen werden, in der sie definiert sind.

Eine SO- Bibliothek unter Linux benötigt keine spezielle Exportanweisung, um exportierbare Symbole anzuzeigen, da alle Symbole für einen Abfrageprozess verfügbar sind.

aleroot
quelle
1
+1 schöne einfache Erklärung. Wenn eine Funktion in einer DLL als "Intern" deklariert ist, kann sie dann nicht von außerhalb der Bibliothek aufgerufen werden?
Mike
23
Es ist nicht unbedingt richtig, dass alle Symbole in einer SO-Bibliothek verfügbar sind. Versteckte Symbole sind möglich und werden empfohlen, da es für Bibliotheksbenutzer keinen guten Grund gibt, alle Ihre Symbole zu sehen.
Zan Lynx
3
Zu __attribute__#define DLLEXPORT __attribute__ ((visibility("default"))) #define DLLLOCAL __attribute__ ((visibility("hidden")))
Brian Haak
33

Ich kann die Details von DLLs in Windows näher erläutern, um meinen Freunden hier in * NIX-land diese Rätsel zu klären ...

Eine DLL ist wie eine Shared Object-Datei. Beide sind Bilder, die vom Programmlader des jeweiligen Betriebssystems in den Speicher geladen werden können. Die Bilder werden von verschiedenen Metadatenbits begleitet, damit Linker und Loader die erforderlichen Zuordnungen vornehmen und die Codebibliothek verwenden können.

Windows-DLLs haben eine Exporttabelle. Die Exporte können nach Namen oder nach Tabellenposition (numerisch) erfolgen. Die letztere Methode wird als "alte Schule" betrachtet und ist viel fragiler. Das Neuerstellen der DLL und das Ändern der Position einer Funktion in der Tabelle führt zu einer Katastrophe, während es kein wirkliches Problem gibt, wenn die Verknüpfung von Einstiegspunkten namentlich erfolgt. Vergessen Sie das als Problem, aber seien Sie sich nur bewusst, dass es vorhanden ist, wenn Sie mit "Dinosaurier" -Code wie den Bibliotheken von Drittanbietern arbeiten.

Windows-DLLs werden durch Kompilieren und Verknüpfen erstellt, genau wie bei einer EXE-Datei (ausführbare Anwendung). Die DLL soll jedoch nicht eigenständig sein, genau wie eine SO von einer Anwendung verwendet werden soll, entweder durch dynamisches Laden oder durch Link-Time-Bindung (der Verweis auf die SO ist in die Metadaten der Anwendungsbinärdatei eingebettet, und der OS-Programmlader lädt die referenzierten SOs automatisch). DLLs können auf andere DLLs verweisen, genauso wie SOs auf andere SOs verweisen können.

In Windows stellen DLLs nur bestimmte Einstiegspunkte zur Verfügung. Diese werden als "Exporte" bezeichnet. Der Entwickler kann entweder ein spezielles Compiler-Schlüsselwort verwenden, um ein Symbol von außen sichtbar zu machen (für andere Linker und den dynamischen Loader), oder die Exporte können in einer Moduldefinitionsdatei aufgelistet werden, die zum Zeitpunkt der Verknüpfung verwendet wird, wenn sich die DLL selbst befindet erschaffen werden. Die moderne Praxis besteht darin, die Funktionsdefinition mit dem Schlüsselwort zu dekorieren, um den Symbolnamen zu exportieren. Es ist auch möglich, Header-Dateien mit Schlüsselwörtern zu erstellen, die dieses Symbol als eines deklarieren, das aus einer DLL außerhalb der aktuellen Kompilierungseinheit importiert werden soll. Weitere Informationen finden Sie in den Schlüsselwörtern __declspec (dllexport) und __declspec (dllimport).

Eines der interessanten Merkmale von DLLs ist, dass sie eine Standard-Handlerfunktion "beim Laden / Entladen" deklarieren können. Immer wenn die DLL geladen oder entladen wird, kann die DLL eine Initialisierung oder Bereinigung durchführen. Dies passt gut zu einer DLL als objektorientiertem Ressourcenmanager, z. B. einem Gerätetreiber oder einer Schnittstelle für gemeinsam genutzte Objekte.

Wenn ein Entwickler eine bereits erstellte DLL verwenden möchte, muss er entweder auf eine "Exportbibliothek" (* .LIB) verweisen, die der DLL-Entwickler beim Erstellen der DLL erstellt hat, oder er muss die DLL zur Laufzeit explizit laden und die anfordern Einstiegspunktadresse nach Namen über die Mechanismen LoadLibrary () und GetProcAddress (). In den meisten Fällen werden DLLs für die Verknüpfung mit einer LIB-Datei verwendet (die lediglich die Linker-Metadaten für die exportierten Einstiegspunkte der DLL enthält). Das dynamische Laden ist normalerweise für die Implementierung von "Polymorphismus" oder "Laufzeitkonfigurierbarkeit" im Programmverhalten reserviert (Zugriff auf Add-Ons oder später definierte Funktionen, auch bekannt als "Plugins").

Die Windows-Vorgehensweise kann manchmal zu Verwirrung führen. Das System verwendet die Erweiterung .LIB, um sowohl auf normale statische Bibliotheken (Archive wie POSIX * .a-Dateien) als auch auf die "Export Stub" -Bibliotheken zu verweisen, die zum Binden einer Anwendung an eine DLL zur Verbindungszeit erforderlich sind. Man sollte also immer nachsehen, ob eine * .LIB-Datei eine gleichnamige * .DLL-Datei hat. Wenn nicht, stehen die Chancen gut, dass die * .LIB-Datei ein statisches Bibliotheksarchiv ist und keine verbindlichen Metadaten für eine DLL exportiert.

JoGusto
quelle
4

Sie haben insofern Recht, als statische Dateien zur Linkzeit in die Anwendung kopiert werden und freigegebene Dateien nur zur Linkzeit überprüft und zur Laufzeit geladen werden.

Der Aufruf von dlopen gilt nicht nur für freigegebene Objekte, wenn die Anwendung dies zur Laufzeit in ihrem Namen tun möchte. Andernfalls werden die freigegebenen Objekte beim Start der Anwendung automatisch geladen. DLLS und .so sind dasselbe. Das dlopen ist vorhanden, um noch feinkörnigere dynamische Ladefunktionen für Prozesse hinzuzufügen. Sie müssen dlopen nicht selbst verwenden, um die DLLs zu öffnen / zu verwenden. Dies geschieht auch beim Start der Anwendung.

Rapadura
quelle
Was wäre ein Beispiel für die Verwendung von dlopen () für mehr Ladekontrolle? Wenn die SO / DLL beim Start automatisch geladen wird, schließt dlopen () sie und öffnet sie beispielsweise mit anderen Berechtigungen oder Einschränkungen erneut? Danke dir.
Wolke
1
Ich glaube, das dlopen ist für Plugins oder ähnliche Funktionen. Die Berechtigungen / Einschränkungen sollten dieselben sein wie beim automatischen Laden, und trotzdem lädt ein dlopen rekursiv abhängige Bibliotheken.
Rapadura
DLL und .sosind nicht genau das gleiche. Siehe diese Antwort
Basile Starynkevitch