Portierung von Qt 5 auf Qt 6 – Die 3 wichtigsten Gründe und Schritte
Die Veröffentlichung von Qt 6 stellt einen bedeutenden Meilenstein dar und bietet zahlreiche neue Funktionen und Verbesserungen gegenüber Qt 5. […]
Fragen Sie sich, wie man D-Bus mit Qt 6 verwendet? Dann sind Sie hier genau richtig! In diesem Beitrag werfen wir einen genaueren Blick auf diesen Mechanismus und vermitteln Ihnen das notwendige theoretische Wissen. Anschließend werden wir versuchen, die Theorie in die Praxis umzusetzen und Ihnen ein Beispiel zu zeigen, wie man dieses System in einer Qt-Anwendung verwendet.
Falls Sie in der Zwischenzeit Unterstützung bei der C++-Entwicklung benötigen, können Sie sich gerne an uns wenden.
D-Bus ist ein Nachrichtensystem, das einen Mechanismus für die Interprozesskommunikation (IPC) ermöglicht, der ursprünglich für Linux entwickelt wurde. Es ist sprachunabhängig, was bedeutet, dass es mit verschiedenen Programmiersprachen verwendet werden kann, einschließlich C++. Durch die Verwendung seiner API in C++ können Sie sowohl Client- als auch Service-Anwendungen erstellen, die miteinander kommunizieren.
D-Bus verwendet ein binäres Nachrichtensendeprotokoll, das eine schnelle und effiziente Kommunikation zwischen Softwareeinheiten, einschließlich Hardwaretreiberdiensten, gewährleistet. Darüber hinaus ermöglicht D-Bus die Interprozesskommunikation über verschiedene Mechanismen, wie zum Beispiel Remote Procedure Calling (RPC), mit dem ein Prozess Methoden oder Funktionen in einem anderen Prozess aufrufen kann.
D-Bus eignet sich gut für die Kommunikation auf derselben Maschine, da es Prozessen, die auf demselben Computer laufen, eine effiziente Informationsweitergabe ermöglicht. Ob es nun um Systemdienste geht, die mit Benutzeranwendungen interagieren, oder um verschiedene Komponenten einer Desktop-Umgebung, die Aufgaben koordinieren – D-Bus vereinfacht und optimiert die Kommunikation zwischen Prozessen auf derselben Maschine. Diese Anpassungsfähigkeit macht dieses Nachrichtensystem zu einem wertvollen Werkzeug zur Förderung der Zusammenarbeit und Modularität in einer Computerumgebung.
Ohne ein einheitliches Bus-System müssten Prozesse direkt miteinander kommunizieren und ein Kanal für die Interprozesskommunikation (IPC) für jede Verbindung einrichten. Mit demselben Bus ist es jedoch möglich, eine viele-zu-viele-Kommunikation unter Verwendung eines einheitlichen Protokolls herzustellen.
Die Interprozesskommunikation bezieht sich auf die Fähigkeit unterschiedlicher Prozesse oder Softwarekomponenten, Informationen auszutauschen und miteinander zu kommunizieren, innerhalb eines Linux- oder Unix-ähnlichen Betriebssystems. D-Bus bietet eine standardisierte und effiziente Möglichkeit für diese normalen Benutzerprozesse, nahtlos miteinander zu interagieren. Es wird häufig mit Hardwaregeräten verwendet, die an einen Hauptmikrocomputer angeschlossen sind. Ein solches System schafft dann ein Punkt-zu-Punkt-Netzwerk.
D-Bus spielt eine entscheidende Rolle beim richtigen Routing von Nachrichten zwischen verschiedenen Prozessen und Diensten. Es stellt sicher, dass Nachrichten an ihre beabsichtigten Ziele weitergeleitet werden, sei es innerhalb einer lokalen Sitzung oder über ein Netzwerk hinweg, und gewährleistet so eine effiziente und sichere Kommunikation zwischen Softwarekomponenten. Die beiden Hauptkomponenten, die für diesen Zweck verwendet werden, sind der Nachrichtensystem-Daemon und die Client-Anwendungen.
Der Daemon (dbus-daemon) ist ein systemweiter Prozess, der die Kommunikation zwischen verschiedenen Anwendungen verwaltet. Er fungiert als Nachrichtenbroker, der es Prozessen ermöglicht, Nachrichten zu senden und zu empfangen. Es gibt zwei Arten dieses Nachrichtensystem-Daemons: einen Systembus und einen Sitzungsbus. Der Systembus ist systemweit und ermöglicht die Kommunikation zwischen verschiedenen Benutzern und Systemdiensten. Der Sitzungsbus ist spezifisch für die Sitzung eines Benutzers und wird für die Kommunikation zwischen Anwendungen innerhalb derselben Benutzersitzung verwendet. Jeder Benutzer hat in der Regel seinen eigenen Sitzungsbus.
Client-Anwendungen sind Prozesse, die D-Bus verwenden, um miteinander zu kommunizieren. Diese Anwendungen können in verschiedenen Programmiersprachen geschrieben werden, einschließlich C++. Sie verwenden die D-Bus-API, um Nachrichten zu senden und zu empfangen. Obwohl D-Bus typischerweise für eine viele-zu-viele-Kommunikation verwendet wird, kann es so konzipiert werden, dass es eine zentrale Serveranwendung gibt, die den Hauptpunkt für alle laufenden Kommunikationsvorgänge darstellt.
Zunächst müssen wir die Terminologie verstehen.
Service ist eine Sammlung von Objektpfaden in einem Session- oder Systembus. Der Service-Prozess hält gemeinsame Objekte, die dank des Adaptors zugänglich sind.
Ein Adaptor wird verwendet, um Methoden, Signale und Eigenschaften in einen Objektpfad zu registrieren. Dank des Adaptors haben wir eine Möglichkeit, unser gemeinsames Objekt, das im Objektpfad abgelegt ist, zu steuern. Ein Objektpfad ist ein Ort, der angibt, wo wir auf ein gemeinsames Objekt zugreifen können. Der Adaptor lebt auf der Seite des Service-Prozesses.
Wenn Sie eine Methode aus dem Objektpfad auf der Seite des Client-Prozesses aufrufen möchten, müssen Sie eine spezifische Schnittstelle (deshalb der Name) verwenden, die dies ermöglicht. Das ist ein Teil von Qt’s Magie. Ein Adaptor und die entsprechende Schnittstelle sollten denselben D-Bus-Schnittstellennamen haben. Sie werden dies später in Aktion sehen.
Nachrichten werden an spezifische Ziele gerichtet, was eine effiziente Kommunikation ohne Broadcasting ermöglicht. Der Client und der Service wissen, wohin Nachrichten gesendet werden sollen, basierend auf den Client- und Service-Bus-Namen. Signalmeldungen, inspiriert vom Signal- und Slot-Mechanismus von Qt, bieten einen „Opt-in“-Ansatz für die Kommunikation von „Eins zu Viele“, sodass mehrere Parteien teilnehmen können.
Um Services und Objektpfade auf einem Session- oder Systembus zu registrieren, verwenden wir die Klasse QDBusConnection.
Um D-Bus zu üben, erstellen wir eine einfache Shop-ähnliche Anwendung, in der wir Produkte erstellen und sie über D-Bus zum Shop hinzufügen. Eine Liste von Produkten wird von der Client-Anwendung abgerufen und mit einer kleinen GUI angezeigt. Auf der Service-Seite haben wir ein Shop-Objekt, das über QDBusConnection geteilt wird.
Als nächstes registrieren wir den D-Bus-Service-Namen, den Objektpfad für den Shop und jede Methode sowie jedes Signal mit Qts Adaptor. Am Ende fügen wir Produkte zur Produktliste des Shops über die Schnittstelle hinzu. Unten ist das GIF mit dem erwarteten Ergebnis:
Der erste Schritt besteht darin, unsere Product-Klasse zu erstellen, die unsere Datenmenge sein wird. Der Datentyp, den wir über D-Bus senden möchten, sollte mit QDbus kompatibel sein. Hier sehen Sie die unterstützten Typen.
Ich habe meine eigene benutzerdefinierte Product-Klasse erstellt, die Name (QString) und Menge (int) enthält. Es ist wichtig, diese wenigen Dinge der Klasse hinzuzufügen, um die D-Bus-Integration zu ermöglichen.
friend QDBusArgument &operator<<(QDBusArgument &argument, const Product &product); friend const QDBusArgument &operator>>(const QDBusArgument &argument, Product &product); static void registerMetaType();
Im registerMetaType-Methoden deklarieren wir alle benötigten Typen, die standardmäßig nicht unterstützt werden. In den QDBusArgument-Operatoren müssen wir deklarieren, welche Struktur wir senden möchten und wie groß diese ist.
void Product::registerMetaType() { qRegisterMetaType<Product>("Product"); qRegisterMetaType<QList<Product>>("QList<Product>"); qDBusRegisterMetaType<Product>(); qDBusRegisterMetaType<QList<Product>>(); } QDBusArgument &operator<<(QDBusArgument &argument, const Product& product) { argument.beginStructure(); argument << product.m_name; argument << product.m_amount; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, Product &product){ argument.beginStructure(); argument >> product.m_name; argument >> product.m_amount; argument.endStructure(); return argument; }
Jetzt können wir unsere Shop-Klasse erstellen. Sie wird unsere Hauptklasse sein, die von QDBusConnection geteilt wird und die Liste der Produkte enthält. Sie wird die Methoden addProduct, getProducts und das productAdded-Signal haben. Wir erstellen eine Klasse wie gewohnt, aber wir müssen Q_CLASSINFO hinzufügen, das zusätzliche Informationen zur Klasse assoziiert, die über QObject::metaObject() zugänglich sind.
Es wird von speziellen Qt-Tools verwendet. Das erste Argument ist der Name der Eigenschaft, und es muss genau so benannt sein, wie es ist. Das zweite ist der Wert und sollte durch Punkte getrennt sein.
class Shop : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "com.scythestudio.dbus.shop") public: explicit Shop(QObject *parent = nullptr) {}; virtual ~Shop() {}; public slots: void addProduct(const Product& product); QList<Product> getProducts(); signals: void productAdded(const Product &product); private: QList<Product> m_products = {Product("Apple", 21), Product("Orange", 37)}; }; void Shop::addProduct(const Product &product) { m_products.append(product); emit productAdded(product); } QList<Product> Shop::getProducts() { return m_products; }
XML-Dateien in D-Bus dienen dazu, die von Diensten angebotenen Schnittstellen und Objekte zu definieren und zu strukturieren. Sie sind sozusagen die D-Bus-Spezifikation. Sie bieten auch Dokumentation, helfen bei der Code-Generierung und enthalten manchmal Zugriffskontrollrichtlinien, die eine standardisierte und sichere Kommunikation zwischen Prozessen gewährleisten.
In diesem Schritt werden wir eine XML-Datei generieren. Dazu verwenden wir ein großartiges, in Qt eingebautes Tool namens qdbuscpp2xml. Es nimmt unsere Shop-Klasse, konvertiert jedes Slot und Signal und speichert es in einer XML-Datei. Der Befehl, den wir verwenden werden, lautet:
qdbuscpp2xml <header file> -o <xml output file>
Achten Sie darauf, dass qdbuscpp2xml nur eine Deklaration der Methoden und Signale mit unterstützten Typen generiert. Wenn der Typ nicht unterstützt wird, müssen Sie die Deklaration manuell hinzufügen.
Zum Beispiel: Ich habe SimpleShop mit einer Methode erstellt.
class SimpleShop : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "com.scythestudio.dbus.shop") public slots: int getNumber() { return 2137; } };
Nachdem ich qdbuscpp2xml SimpleShop.h -o SimpleShop.xml ausgeführt habe, habe ich die Ausgabe-Datei SimpleShop.xml erhalten:
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> <node> <interface name="com.scythestudio.dbus.shop"> <method name="getNumber"> <arg type="i" direction="out"/> </method> </interface> </node>
Wenn wir es auf das ursprüngliche Shop.h ausführen, das keine Slots mit unterstützten Typen enthält, wird die Datei Shop.xml leer sein und die Konsole wird keine Fehlermeldungen zurückgeben.
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> <node> </node>
Also müssen wir es manuell schreiben. Aber keine Sorge, ich werde Sie durch den Prozess führen!
In der XML-Datei verwenden wir Markierungen wie <interface>, <method>, <signal>, <arg> und <annotation>.
<interface> beschreibt die Schnittstelle, die verwendet wird, um unser freigegebenes Objekt zu steuern.
<method> ist für die Registrierung einer Methode unseres freigegebenen Objekts.
<signal> dient der Registrierung von Signalen aus unserem freigegebenen Objekt.
<arg> ist für Argumente, die in oder aus einer Methode oder einem Signal kommen.
<annotation> ist eine Referenz auf unseren benutzerdefinierten Typ in <arg>. Wenn das Argument ein unterstützter Typ von D-Bus ist, müssen wir dies nicht einfügen.
Als Beispiel können wir die Methode addProduct aus der Shop.h-Datei nehmen.
void addProduct(const Product& product);
addProduct hat:
Den Methodennamen „addProduct“.
Ein Argument mit dem Namen „product“ und dem Typ „Product“. Produkt ist eine Klasse mit einem Integer und einem String, also ist der Typ (is). Dieses Argument kommt mit einer Methode, also ist die Richtung „in“. Wenn die Methode einen Product zurückgibt, wäre die Richtung des Arguments „out“. Wenn wir mehrere Argumente haben, müssen wir mehr <arg … /> Markup hinzufügen.
Da Product ein benutzerdefinierter Typ ist, müssen wir eine Annotation schreiben. Der Name (org.qtproject.QtDBus.QtTypeName.) bleibt immer gleich, aber der letzte Teil ändert sich. „In0“ bedeutet, dass unser benutzerdefinierter Typ der erste in der Argumentenreihenfolge ist. Im Wert müssen wir den deklarierten Typ (durch Q_DECLARED_METATYPE(<Type>)) angeben. Dasselbe gilt für zurückgegebene Argumente, aber statt „In0“ sollte es „Out0“ sein. Die Nummer der Annotationss sollte der Anzahl der benutzerdefinierten Argumenttypen entsprechen.
Unsere Methode addProduct sollte also folgendermaßen aussehen:
<method name="addProduct"> <arg name="product" type="(is)" direction="in"/> <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="Product"/> </method>
Um es besser zu verstehen, kann ich ein Beispiel für die Methode removeProduct angeben, bei der ein unterstütztes Argument und zwei benutzerdefinierte Argumente vorkommen.
void removeProduct(const Product& product, int index, const Date& date);
Nachdem wir alle Markups für die Methoden und Signale von Shop geschrieben haben, sollte unsere vollständige Shop.xml so aussehen:
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> <node> <interface name="com.scythestudio.dbus.shop"> <method name="addProduct"> <arg name="product" type="(is)" direction="in"/> <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="Product"/> </method> <method name="getProducts"> <arg type="a(is)" direction="out"/> <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList<Product>"/> </method> <signal name="productAdded"> <arg type="(is)" direction="out"/> <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="Product"/> </signal> </interface> </node>
Wir haben nun die Produkt-, Shop-Klasse und die Shop-XML-Datei fertig. Wir sind fast am Ziel! Jetzt kommt die Qt-Magie!
Um eine D-Bus-Schnittstelle und die D-Bus-Adaptor-C++-Klassen zu erstellen, verwenden wir `qdbusxml2cpp`.
Wir werden den folgenden Befehl verwenden:
qdbusxml2cpp <xml file> -i <h file> -a <adaptor file output> qdbusxml2cpp <xml file> -i <h file> -p <interface file output>
-i steht für die Dateien, die wir in unsere Ausgabedateien einfügen möchten. Wir müssen alle Datensätze importieren, die der Adaptor und das Interface verwenden.
-a ist für die Ausgabedatei des Adaptors.
-p ist für die Ausgabedatei des Interfaces.
In unserem Fall wird der Befehl folgendermaßen aussehen:
qdbusxml2cpp Shop.xml -i Product.h -a ShopAdaptor qdbusxml2cpp Shop.xml -i Product.h -p ShopInterface
Denken Sie daran, dass Sie den Befehl mit Dateien in anderen Verzeichnissen ausführen sollten, wo sich Ihr Adaptor/Interface beim Erstellen Ihres Projekts befinden wird. qdbusxml2cpp nimmt Ihre Argumente und fügt sie direkt in die Importe ein, daher ist es wichtig sicherzustellen, dass jeder Pfad korrekt ist.
Das war’s. Wir haben einen Adaptor und ein Interface für die Shop-Klasse erstellt.
Dies könnte tatsächlich auch mit CMake erreicht werden. Weitere Informationen finden Sie in Qt’s Dokumentation.
Um einen Service-Prozess zu starten, müssen wir zwei weitere Dinge in unserer Shop-Klasse tun.
Deklarieren Sie ShopAdaptor als Mitglied der Shop-Klasse und instanziieren Sie es mit einem Zeiger auf dieses Objekt als Elternteil. Dadurch erstellen wir eine Verbindung, sodass beim Aufrufen einer Interface-Methode eine Nachricht übergeben wird und die Methode von einem freigegebenen Objekt im Service-Prozess ausgeführt wird.
Im startDBusService-Methodenaufruf verbinden wir den Adaptor mit diesem Shop-Objekt, registrieren den D-Bus-Service (unter Angabe des Netzwerknamens mit Punkten getrennt), den Objektpfad und teilen dieses Shop-Objekt.
void Shop::startDBusService() { Product::registerMetaType(); m_adaptor = new ShopAdaptor(this); auto connection = QDBusConnection::sessionBus(); if (!connection.registerService("com.scythestudio.dbus")) { qFatal("Could not register service!"); } if (!connection.registerObject("/shop", this)) { qFatal("Could not register Chat object!"); } }
Jetzt sind wir bereit, den Service-Prozess zu starten. Nachdem wir den Service-Prozess gestartet haben, können wir überprüfen, ob alles einwandfrei funktioniert. Dazu verwenden wir die Befehlszeilen-Tools von Qt.
qdbus – zeigt alle laufenden Dienste an.
~$ qdbus (...) :1.295 com.scythestudio.dbus (...) qdbus <service> – shows declared object paths in the service. ~$ qdbus com.scythestudio.dbus / /shop dbus-send –print-reply –dest=<service> <object path> <interface>.<method name> – can print a reply for object method. ~$ dbus-send --print-reply --dest=com.scythestudio.dbus /shop com.scythestudio.dbus.shop.getProducts method return time=1696932139.293770 sender=:1.101 -> destination=:1.104 serial=3 reply_serial=2 array [ struct { string "Apple" int32 21 } struct { string "Orange" int32 37 } ]
Es funktioniert einwandfrei, oder?
Der letzte Schritt ist, den ShopController zu erstellen, der nichts mit der D-Bus-Spezifikation zu tun hat. Es dient nur dazu, dieses Beispiel vollständiger zu machen und etwas mit der Benutzeroberfläche zu tun. Er wird dem QML mit einer Liste von Produkten, die vom gemeinsamen Shop-Objekt abgerufen werden, zur Verfügung gestellt. Dies ist dank des ShopInterface möglich, das uns die Kontrolle über das geteilte „Shop“-Objekt ermöglicht.
Im Konstruktor von ShopController müssen wir das Interface mit dem Service verbinden und den Produkttyp registrieren.
m_interface = new ComScythestudioDbusShopInterface("com.scythestudio.dbus", "/shop", QDBusConnection::sessionBus(), this); Product::registerMetaType();
Wir sind bereit! Wir können auf den entfernten Shop über ein Interface zugreifen!
Wenn wir eine Produktliste abrufen möchten…
m_interface->getProducts().value();
Und fügen Sie der Liste ein Produkt hinzu.
m_interface->addProduct(product);
Ziemlich cool, oder?
An diesem Punkt sollten Sie wissen, was der D-Bus-Mechanismus ist und wie Sie diese Art der Interprozesskommunikation in Ihrem Projekt implementieren können. Wenn Sie noch auf Probleme stoßen, ziehen Sie unsere Qt-Entwicklungsdienste in Betracht. Wir können Ihnen sicherlich bei den meisten Aspekten des Qt-, QML- und C++-Codelings helfen.
Wenn Sie den gesamten Code des Beispiels für diesen Beitrag erhalten möchten, besuchen Sie unser GitHub-Profil. Weitere Inhalte zum Qt-Framework finden Sie in unseren anderen Blogposts. Folgen Sie dem LinkedIn-Profil von Scythe Studio, um immer auf dem neuesten Stand zu bleiben!
Kommen wir zur Sache: Es ist eine Herausforderung, Top-Qt-QML-Entwickler zu finden. Helfen Sie sich selbst und starten Sie die Zusammenarbeit mit Scythe Studio – echten Experten im Qt C++ Framework.
Entdecken Sie unsere Fähigkeiten!Die Veröffentlichung von Qt 6 stellt einen bedeutenden Meilenstein dar und bietet zahlreiche neue Funktionen und Verbesserungen gegenüber Qt 5. […]
Das industrielle Internet der Dinge (IIoT) revolutioniert die Art und Weise, wie Industrien arbeiten, indem es intelligenteres und effizienteres Fertigen […]
Die heutige Medizin wäre nicht an dem Punkt, an dem sie sich befindet, wenn softwarebasierte Lösungen nicht in sie implementiert […]