Heute sehen wir uns an, wie Sie Bindungen für Ihr eigenes Projekt erstellen können.
The Qt Company freut sich bekannt zu geben, dass Qt for Python auch Shiboken, Ihr primäres Bindungstool, enthalten wird.
Lesen Sie das folgende Material und Sie erhalten eine Vorstellung davon, wie Sie Python-Bindungen für eine einfache C++-Bibliothek erstellen.
Hoffentlich ermutigt Sie dies, dasselbe mit Ihren eigenen benutzerdefinierten Bibliotheken zu tun.
Wie bei jedem Qt-Projekt stellen wir gerne Artikel über Shiboken zur Verfügung, um so das Verständnis für alle zu verbessern.
Beispielbibliothek
Der Hauptzweck dieses Beitrags wird unsere Verwendung einer etwas unsinnigen benutzerdefinierten Bibliothek namens Universe sein. Es bietet zwei Klassen: Icecream (Eiscreme) und Truck (LKW).
Icecreams zeichnet sich durch Geschmack aus und der Truck dient als Lieferfahrzeug für Icecream an Kinder in der Nachbarschaft. Ziemlich einfach.
Wir möchten diese Klassen in Python verwenden. Der Anwendungsfall besteht darin, zusätzliche Eissorten hinzuzufügen oder zu überprüfen, ob die Eislieferung erfolgreich war. Vereinfacht gesagt wollen wir Python-Bindings für Icecream und Truck bereitstellen, damit wir sie in unserem eigenen Python-Skript verwenden können.
Wir werden der Kürze halber einige Teile überspringen, aber Sie können das Repository für den vollständigen Quellcode überprüfen. Link zum Repository pyside-setup/examples/samplebinding .
C++-Bibliothek
Werfen wir zunächst einen Blick auf die Icecream-Header-Datei
class Icecream { public: Icecream(const std::string &flavor); virtual Icecream *clone(); virtual ~Icecream(); virtual const std::string getFlavor(); private: std::string m_flavor; };
und Header-Datei Truck
class Truck { public: Truck(bool leaveOnDestruction = false); Truck(const Truck &other); Truck& operator=(const Truck &other); ~Truck(); void addIcecreamFlavor(Icecream *icecream); void printAvailableFlavors() const; bool deliver() const; void arrive() const; void leave() const; void setLeaveOnDestruction(bool value); void setArrivalMessage(const std::string &message); private: void clearFlavors(); bool m_leaveOnDestruction = false; std::string m_arrivalMessage = "A new icecream truck has arrived!\n"; std::vector m_flavors; };
Die meisten APIs sollten ziemlich einfach zu verstehen sein, aber wir fassen die wichtigen Teile zusammen:
- Icecream ist ein polymorpher Typ und soll überschrieben werden
- getFlavor() gibt den Flavor abhängig vom tatsächlichen abgeleiteten Typ zurück
- Truck speichert einen Vektor von Icecream-Objekten, die er besitzt, die über addIcecreamFlavor() hinzugefügt werden können
- LKW-Ankunftsnachricht kann mit setArrivalMessage() konfiguriert werden
- liefern() teilt uns mit, ob die "Eis"-Lieferung erfolgreich war oder nicht
System vom Shiboken-Typ
Um Shiboken über die API zu informieren, benötigen wir Bindungen. Dazu erstellen wir eine Header-Datei, die die Typen enthält, an denen wir interessiert sind:
#ifndef BINDINGS_H #define BINDINGS_H #include "icecream.h" #include "truck.h" #endif // BINDINGS_H
Darüber hinaus erfordert Shiboken auch eine XML-Typsystemdatei, die die Beziehung zwischen C++- und Python-Typen definiert:
<?xml version="1.0"?> <typesystem package="Universe"> <primitive-type name="bool"/> <primitive-type name="std::string"/> <object-type name="Icecream"> <modify-function signature="clone()"> <modify-argument index="0"> <define-ownership owner="c++"/> </modify-argument> </modify-function> </object-type> <value-type name="Truck"> <modify-function signature="addIcecreamFlavor(Icecream*)"> <modify-argument index="1"> <define-ownership owner="c++"/> </modify-argument> </modify-function> </value-type> </typesystem>
Als Erstes ist zu beachten, dass wir „bool“ und „std::string“ als primitive Typen deklarieren. Einige der C++-Methoden verwenden sie als Parameter-/Rückgabetypen und daher muss der Shiboken über sie Bescheid wissen. Es kann dann den entsprechenden Konvertierungscode zwischen C++ und Python generieren.
Die meisten primitiven C++-Typen werden von Shiboken verarbeitet, ohne dass zusätzlicher Code erforderlich ist. Dann deklarieren wir die beiden obigen Klassen. Eines davon als "object-type" (object-type) und das andere als "value-type" (value-object).
Der Hauptunterschied besteht darin, dass „Objekttyp“ im generierten Code als Zeiger übergeben werden, während „Werttyp“ kopiert wird (Wertsemantik).
Durch das Festlegen der Klassennamen in der Typsystemdatei versucht Shiboken automatisch, Bindungen für alle in den Klassen deklarierten Methoden zu erstellen, sodass nicht alle Methodennamen manuell angegeben werden müssen ...
Wenn Sie die Funktion in keiner Weise ändern möchten, bringt uns das zum nächsten Thema, Eigentumsregeln.
Shiboken kann nicht auf magische Weise herausfinden, wer für die Freigabe von in Python-Code platzierten C++-Objekten verantwortlich ist.
Es kann viele Fälle geben: Python muss den C++-Speicher freigeben, wenn die Anzahl der Verweise auf ein Python-Objekt Null wird, oder Python darf niemals ein C++-Objekt löschen, vorausgesetzt, es wird irgendwann in der C++-Bibliothek gelöscht. Oder vielleicht ist es das Elternobjekt eines anderen Objekts (wie zum Beispiel QWidgets). In unserem Fall wird die clone()-Methode nur innerhalb der C++-Bibliothek aufgerufen, und wir gehen davon aus, dass der C++-Code dafür sorgt, dass der Speicher des geklonten Objekts freigegeben wird.
Was addIcecreamFlavor() betrifft, wissen wir, dass sich der Truck im Icecream-Objekt befindet und sofort entfernt wird, nachdem der Truck zerstört wurde. Der Besitz ist also wieder auf „c++“ eingestellt.
Wenn wir keine Eigentumsregeln angegeben hätten, würden C++-Objekte gelöscht, wenn die entsprechenden Python-Namen den Gültigkeitsbereich verlassen würden.
Montage
Um eine benutzerdefinierte Universumsbibliothek zu erstellen und dann Bindungen dafür zu generieren, stellen wir eine gut dokumentierte, meist generische CMakeLists.txt-Datei bereit, die Sie für Ihre eigenen Bibliotheken wiederverwenden können.
Es läuft im Grunde darauf hinaus, „cmake.“ aufzurufen, um das Projekt einzurichten, und dann mit der Werkzeugkette Ihrer Wahl zu bauen (wir empfehlen den „(N) Makefiles“-Generator).
Als Ergebnis der Erstellung eines Projekts erhalten Sie zwei gemeinsam genutzte Bibliotheken: libuniverse. (so/dylib/dll) und Universum. (so/pyd).
Die erste ist eine benutzerdefinierte C++-Bibliothek und die letzte ein Python-Modul, das aus einem Python-Skript (Dokument, Testtreiber) importiert werden kann.
Natürlich gibt es auch von Shiboken erstellte Zwischendateien (.h/.cpp-Dateien, die zum Erstellen von Python-Bindungen erstellt wurden). Machen Sie sich darüber keine Sorgen, wenn Sie keine Fehlerbehebung benötigen oder wenn es aus irgendeinem Grund nicht kompiliert werden kann oder sich nicht so verhält, wie es sollte. Dann können Sie einen Fehlerbericht an Qt Company senden!
Und schließlich kommen wir zum Python-Teil.
Verwenden des Python-Moduls
Im nächsten kleinen Skript werden wir das Universe-Modul verwenden, von Icecream erben, virtuelle Methoden implementieren, Objekte instanziieren und vieles mehr.
from Universe import Icecream, Truck class VanillaChocolateIcecream(Icecream): def __init__(self, flavor=""): super(VanillaChocolateIcecream, self).__init__(flavor) def clone(self): return VanillaChocolateIcecream(self.getFlavor()) def getFlavor(self): return "vanilla sprinked with chocolate" class VanillaChocolateCherryIcecream(VanillaChocolateIcecream): def __init__(self, flavor=""): super(VanillaChocolateIcecream, self).__init__(flavor) def clone(self): return VanillaChocolateCherryIcecream(self.getFlavor()) def getFlavor(self): base_flavor = super(VanillaChocolateCherryIcecream, self).getFlavor() return base_flavor + " and a cherry" if __name__ == '__main__': leave_on_destruction = True truck = Truck(leave_on_destruction) flavors = ["vanilla", "chocolate", "strawberry"] for f in flavors: icecream = Icecream(f) truck.addIcecreamFlavor(icecream) truck.addIcecreamFlavor(VanillaChocolateIcecream()) truck.addIcecreamFlavor(VanillaChocolateCherryIcecream()) truck.arrive() truck.printAvailableFlavors() result = truck.deliver() if result: print("All the kids got some icecream!") else: print("Aww, someone didn't get the flavor they wanted...") if not result: special_truck = Truck(truck) del truck print("") special_truck.setArrivalMessage("A new SPECIAL icecream truck has arrived!\n") special_truck.arrive() special_truck.addIcecreamFlavor(Icecream("SPECIAL *magical* icecream")) special_truck.printAvailableFlavors() special_truck.deliver() print("Now everyone got the flavor they wanted!") special_truck.leave()
Nachdem wir die Klassen aus unserem Modul importiert haben, erstellen wir zwei abgeleitete (sekundäre) Icecream-Typen, die mit Geschmacksrichtungen angepasst wurden.
Dann erstellen wir einen LKW (LKW), fügen einige reguläre Varianten (Sorten) von Icecreams (Eiscreme) und zwei spezielle hinzu.
Wir versuchen, Icecream (Eiscreme) zu versenden (zu liefern).
Wenn die Lieferung fehlschlägt, erstellen wir einen neuen LKW mit den alten kopierten Optionen und einer neuen magischen Option, die sicherlich alle Kunden zufrieden stellen wird.
Das obige Skript zeigt kurz die Verwendung von C++-Typrückschlüssen, das Überschreiben virtueller Methoden, das Erstellen und Zerstören von Objekten usw.