Gemeinsame Bibliotheken erstellen

In den folgenden Abschnitten werden einige Dinge aufgelistet, die bei der Erstellung von Shared Libraries beachtet werden sollten.

Verwendung von Symbolen aus Shared Libraries

Symbole - Funktionen, Variablen oder Klassen -, die in gemeinsam genutzten Bibliotheken enthalten sind und von Clients wie Anwendungen oder anderen Bibliotheken verwendet werden sollen, müssen auf besondere Weise gekennzeichnet werden. Diese Symbole werden als öffentliche Symbole bezeichnet, die exportiert oder öffentlich sichtbar gemacht werden.

Die übrigen Symbole sollten von außen nicht sichtbar sein. Auf den meisten Plattformen werden sie von den Compilern standardmäßig ausgeblendet. Auf einigen Plattformen ist eine spezielle Compileroption erforderlich, um diese Symbole zu verbergen.

Beim Kompilieren einer gemeinsam genutzten Bibliothek muss diese für den Export markiert werden. Um die Shared Library von einem Client aus zu verwenden, kann auf einigen Plattformen auch eine spezielle Import-Deklaration erforderlich sein.

Je nach Zielplattform stellt Qt spezielle Makros zur Verfügung, die die notwendigen Definitionen enthalten:

  • Q_DECL_EXPORT muss zu den Deklarationen der Symbole hinzugefügt werden, die beim Kompilieren einer Shared Library verwendet werden.
  • Q_DECL_IMPORT muss zu den Deklarationen der Symbole hinzugefügt werden, die beim Kompilieren eines Clients verwendet werden, der die Shared Library nutzt.

Nun müssen wir sicherstellen, dass das richtige Makro aufgerufen wird - egal, ob wir eine Shared Library selbst kompilieren oder nur den Client, der die Shared Library verwendet. Normalerweise kann dies durch Hinzufügen eines speziellen Headers gelöst werden.

Nehmen wir an, wir wollen eine Shared Library namens mysharedlib erstellen. Ein spezieller Header für diese Bibliothek, mysharedlib_global.h, sieht wie folgt aus:

#include <QtCore/QtGlobal>

#if defined(MYSHAREDLIB_LIBRARY)
#  define MYSHAREDLIB_EXPORT Q_DECL_EXPORT
#else
#  define MYSHAREDLIB_EXPORT Q_DECL_IMPORT
#endif

In jedem Header der Bibliothek geben wir Folgendes an:

#include "mysharedlib_global.h"

MYSHAREDLIB_EXPORT void foo();
class MYSHAREDLIB_EXPORT MyClass...

Wir müssen dann sicherstellen, dass MYSHAREDLIB_LIBRARY für den Compiler definiert ist, wenn wir die Bibliothek selbst bauen. Dies wird im Build-System der Bibliothek gemacht. Wenn wir CMake verwenden, fügen wir das Ziel der gemeinsamen Bibliothek hinzu:

target_compile_definitions(mysharedlib PRIVATE MYSHAREDLIB_LIBRARY)

Wenn wir qmake verwenden, fügen wir

DEFINES += MYSHAREDLIB_LIBRARY

zur .pro Datei der Shared Library hinzu.

Anmerkung: Die Bibliotheks-Assistenten in Qt Creator und Qt VS Tools stellen Ihnen ein Skelett zur Verfügung, das diese Dinge für Sie einrichtet.

Überlegungen zur Header-Datei

Normalerweise werden Clients nur die öffentlichen Header-Dateien von gemeinsam genutzten Bibliotheken einbinden. Diese Bibliotheken können an einem anderen Ort installiert werden, wenn sie bereitgestellt werden. Daher ist es wichtig, andere interne Header-Dateien, die bei der Erstellung der gemeinsam genutzten Bibliothek verwendet wurden, auszuschließen.

Beispielsweise könnte die Bibliothek eine Klasse bereitstellen, die ein Hardwaregerät umschließt und ein Handle zu diesem Gerät enthält, das von einer Bibliothek eines Drittanbieters bereitgestellt wird:

#include <footronics/device.h>

class MyDevice {
private:
    FOOTRONICS_DEVICE_HANDLE handle;
};

Eine ähnliche Situation ergibt sich bei Formularen, die mit Qt Widgets Designer erstellt wurden, wenn Aggregation oder Mehrfachvererbung verwendet wird:

#include "ui_widget.h"

class MyWidget : public QWidget {
private:
    Ui::MyWidget m_ui;
};

Beim Einsatz der Bibliothek sollte keine Abhängigkeit von den internen Headern footronics/device.h oder ui_widget.h bestehen.

Dies kann vermieden werden, indem das Idiom Pointer to implementation verwendet wird, das in verschiedenen C++-Programmierbüchern beschrieben wird. Für Klassen mit Wertesemantik sollten Sie QSharedDataPointer verwenden.

Binäre Kompatibilität

Damit Clients, die eine gemeinsam genutzte Bibliothek laden, korrekt funktionieren, muss das Speicherlayout der verwendeten Klassen genau mit dem Speicherlayout der Bibliotheksversion übereinstimmen, die zum Kompilieren des Clients verwendet wurde. Mit anderen Worten, die vom Client zur Laufzeit gefundene Bibliothek muss binärkompatibel mit der zur Kompilierzeit verwendeten Version sein.

Dies ist in der Regel kein Problem, wenn der Client ein in sich geschlossenes Softwarepaket ist, das alle benötigten Bibliotheken mitliefert.

Wenn die Client-Anwendung jedoch auf eine gemeinsam genutzte Bibliothek angewiesen ist, die zu einem anderen Installationspaket oder zum Betriebssystem gehört, müssen wir uns ein Versionsschema für gemeinsam genutzte Bibliotheken überlegen und entscheiden, auf welcher Ebene die Binärkompatibilität aufrechterhalten werden soll. Zum Beispiel sind Qt-Bibliotheken mit der gleichen Hauptversionsnummer garantiert binärkompatibel.

Die Aufrechterhaltung der Binärkompatibilität bringt einige Einschränkungen bei den Änderungen mit sich, die Sie an den Klassen vornehmen können. Eine gute Erklärung finden Sie unter KDE - Policies/Binary Compatibility Issues With C++. Diese Probleme sollten bereits zu Beginn der Bibliotheksentwicklung berücksichtigt werden. Wir empfehlen, das Prinzip des Information Hiding und die Pointer-to-Implementation-Technik zu verwenden, wo immer dies möglich ist.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.