C++ Bibliothek in Swift (os x und iOS) verwenden. C++ to Swift Class Wrapping

gepostet am 08.01.2017 um 02:01 Uhr

Swift als Hauptsprache für die Entwicklung von os x und iOS war wohl eine der schönsten Änderungen im SDK von Apple. Und das Entwickeln von Applikationen in Swift ist eine sehr schöne und elegante Angelegenheit, auch wenn es manchmal in der Denkweise ein wenig Umdenken benötigt. Dennoch ist jeder Code der in Swift innerhalb des Cocoa oder des UIKit Frameworks erstellt wurde nur auf den dafür erstellten Systemen lauffähig. Was ist nun, wenn man eine App nicht nur für ein System entwickeln möchte (und hier rede ich nicht von os x und iOS sondern von iOS, Android, os x und Windows)? Dann kommt man nicht darum herum für jedes System Code zu schreiben und damit sehr viele Stellen zu schaffen an denen man bei entdeckten Bugs nachbessern muss. Oder etwa doch?

Und ihr wisst worauf ich hinaus will. Es geht um Bibliotheken die man überall benutzen kann. Man schreibt also plattformunabhängigen Code in eine Bibliothek und verwendet diese dann in den jeweiligen Projekten. So ist zumindest der UI-unabhängige Teil der Logik nur einfach geschrieben und muss nicht immer wieder erneut erstellt werden.

Nun können wir uns entscheiden welche Sprache wir verwenden wollen um die Codes einfach zu erstellen und auf allen Plattformen lauffähig zu machen. Welche Sprache bietet sich da besser an als C++? Es gibt natürlich auf die Möglichkeit C zu verwenden (was ehrlich gesagt in Verbindung mit Swift auch einfacher währe, da C Funtkionen in Swift direkt aufrufbar wären) aber wir wollen doch objektorientiert bleiben.

Übrigens wird oft behauptet C++ sei viel schneller als Swift… Ich wage zu bezweifeln dass wir mit dieser Technik hier tatsächlich Laufzeit sparen. Also sollte das euer Grund hierfür sein, ich glaube damit seid ihr auf dem Holzweg. Allerding gibt es einige schöne Bibliotheken für C++ die eben nur hierfür implementiert (oder so gut implementiert) sind und so können wir sie nutzen.

Also schreiben wir unsere eigene statische C++ Bibliothek, erstellen unsere Klassen und Funktionen und implementieren unsere Logik. Aber wie können wir den Code nun in Swift verwenden? Darauf möchte ich nun tiefer eingehen.

Aber zuerst…

 

Was wir brauchen.

Für dieses Tutorial benötigen wir mindestens einen Texteditor und Xcode. Da ich nicht geisteskrank bin und gerne Tools verwende die uns andere Entwickler bereits gemacht haben, benutze ich für den C++ Part CLion. Das ist allerdings eine IDE die etwas kostet (was Sinn macht, denn so gute IDEs wie die von JetBrains bekommt man sonst nicht), ihr könnt also auch Eclipse oder NetBeans verwenden wenn ihr wollt.

Wenn ihr eure eigene C++ Bibliothek schreiben wollt, solltet ihr zumindest ein wenig Ahnung über C++ haben. Kenntnisse in CMake sind von Vorteil, vor Allem wenn ihr keine IDE verwenden wollt. Kenntnisse in Xcode und Swift sind Grundvoraussetzung.

 

Schreiben wir unsere eigene C++ Bibliothek…

Diesen Schritt könnt ihr überspringen wenn ihr eine bereits vorhandene Bibliothek verwenden wollt. Allerdings ist wichtig dass ihr alle Header der Bibliothek habt. Ich werde darauf verzichten auf die Einzelheiten von C++ einzugehen.

Für das Tutorial habe ich mir eine einfache Struktur überlegt, was nicht sonderlich schwierig war. Wir erstellen eine Klasse Person, von der die Klasse Employee und die Klasse Boss erben. Einem Employee kann ein Boss gegeben werden und der Employee spuckt einem den Namen des Boss verbunden mit dessen Alter aus. Das ist die Logik die wir nicht noch einmal in Swift entwickeln wollen. Natürlich ist das für diese kleine Logik übertrieben. Ihr solltet euch einfach vorstellen ihr habt eine ausladend große Business Logic im Hintergrund.

Fangen wir also an unsere C++ Klassen zu erstellen:

Person.h

 

Person.cpp

 

Boss.h

 

Boss.cpp

 

Employee.h

 

Employee.cpp

Daraus baue ich dann eine statische Bibliothek. Wie man das macht kann man in vielen Tutorials über CMake einsehen. Daher werde ich mich nicht weiter damit beschäftigen. Wenn ihr eine IDE benutzt reicht es ohnehin einfach zu bauen. Da ich CLion verwende kann ich einfach auf Build klicken und bekomme im Outputverzeichnis mein *.a file. Dieses stellt nun unsere C++ Bibliothek dar.

 

Ab nach Swift…

Nun haben wir unsere C++ Bibliothek die unsere Logik enthält die wir plattformübergreifend nur ein Mal implementieren wollen. Lasst uns das Ganze nun in Swift integrieren und dabei die Logik wiederverwenden. Fangen wir mit einem neuen Xcode Projekt an. Das ist aber auch auf ein vorhandenes Projekt übertragbar.

Ich wähle ein Command Line Tool.

 

 

 

 

 

 

 

 

Projektinfos: 

 

 

 

 

 

 

 

 

 

Danach müsst ihr die erstellte Bibliothek zum Projekt hinzufügen und die Datei als Bibliothek unter dem Punkt “Link Binary with Libraries” angeben:

 

 

 

 

 

 

Gleichzeitig müssen wir alle Header die wir innerhalb der Bibliothek nutzen (auch diese die von anderen Headern importiert werden) zu unserem Xcode Projekt hinzufügen. Das ist wichtig, da wir die Header importieren müssen, um die Symbole bekannt zu machen. Achtet darauf dass ihr die Header nicht zu dem Target hinzufügt. Unser Projekt schaut nun also so aus:

Damit wären wir auch schon bereit die C++ Bibliothek zu nutzen. Das einzige Problem das wir nun haben ist, dass Swift nur direkt mit C kann, nicht aber mit C++. Das umgehen wir indem wir uns einen Wrapper schreiben. Dieser ist eine normale C++ Datei (*.cpp) und mappt uns sozusagen die C++ Codes so um, dass sie von C gelesen und ausgeführt werden können.

Also fügen wir eine neue Datei hinzu, die ich wrapper.cpp nenne:

Achtet darauf dass ihr keinen Header dafür erstellt.

 

 

 

 

 

 

 

Wenn Xcode fragt ob es einen Bridging-Header erstellen soll, dann bestätigt dies, wir benötigen diesen Header noch:

 

In dieser Datei wrappen wir nun alle unsere C++ Funktionen die wir benötigen werden. Wichtig ist dabei dass wir nun ein wenig umdenken müssen. Denn wir müssen aus C++ Funktionalitäten C Code machen. Das bedeutet dass wir auf ein Mal keine Objektorientierung mehr haben. Daher müssen wir uns Gedanken darüber machen wie wir unsere Instanzzeiger verwalten. Ich möchte hier einen Ansatz zeigen wie wir auf Seite von Swift wieder eine Objektorientierung erhalten. Daher werde ich mich in dieser Datei nicht darum kümmern was mit meinen Pointern passiert. Ich muss mich aber sehr wohl darum kümmern dass der Speicher den ich erzeuge auch wieder freigegeben wird. Immerhin befinden wir uns hier in einem C++ Kontext und hier ist der Entwickler für Speichermanagement verantwortlich. Also erzeuge ich auch Funktionen die mir den Speicher wieder deallokieren. Mappen wir also erstmal unser Boss Objekt. Die Klasse Person überspringe ich jetzt bewusst weil ich sie nicht benötige.

Ok. Gehen wir den Code mal durch. Der Begriff extern "C" sagt dem C++ Compiler der den Code bearbeitet, dass diese Funktionen die hier angegeben werden, von C aus aufrufbar sein müssen.

  • Mit der ersten Funktion void * boss_init(const char *name, const int age, const char *department); rufe ich den Konstruktor der Klasse Boss auf. Wer C++ (und C) kennt, weiß dass hier ein Pointer auf eine dynamisch allokierte Speicherstelle erzeugt werden muss, da sonst das Objekt im Stack abgelegt wird und der mit dem austreten aus der Funktion zurückgesetzt (hochgezählt) wird. Daher erzeugen ich ein Objekt mit new Boss(string(name), age, string(department));. Die strings muss ich, da ja die Funktionen von C aus erreichbar sein müssen, als char Arrays übergeben. Auch der Rückgabewert muss ein Voidpointer sein, da Klassen in C nicht bekannt sind.
  • Die Funktion void boss_deconstructor(const void *const boss); gebe ich den Speicher des Objektes wieder frei. Diese Funktion rufe ich dann auf wenn wir das Objekt nicht mehr benötigen. Der Funktion wird ein Pointer auf das Objekt übergeben. Dieser ist aufgrund von C auch wieder Void und wird erst innerhalb der Funktion gecastet.
  • Jede weitere Funktion die nun mit dem Objekt arbeiten muss, bekommt einen Pointer auf das Objekt (wie der Destruktor) da wir ja nicht Objektorientiert arbeiten können.

 

Nun haben wir C seitig alle Funktionen gewrapped um mit dem C++ Objekt arbeiten zu können. Damit wir diese Funktionen in Swift benutzen können, müssen wir sie Swift bekannt machen. Dafür müssen wir die Funktionen im Bridging Header deklarieren:

Wir hätten natürlich auch ein *.h file für wrapper.cpp erstellen können und dort diese Funktionen deklarieren können und dieses File dann hier einfach importieren können. Aber warum so umständlich.

 

Ab jetzt können wir die Funktionen in Swift verwenden. Wir wollen allerdings nicht einfach so sinnlos damit arbeiten sondern in Swift wieder objektorientiert werden. Daher erstellen wir eine Klasse Boss in Swift die die C++ Klasse Boss in vertritt. Gleichzeitig schaffen wir uns einen einfachen Weg uns um das Anfragen und Freigeben des Speichers zu kümmern und halten uns an einer Stelle den Pointer auf das C++ Objekt.

Aber kurz davor möchte ich uns noch eine kurze Erleichterung schaffen indem ich String erweitere damit wir uns einfach char Arrays aus dem String erzeugen können.

 

Nun zur Klasse Boss:

 

Das wichtige ist nun, dass diese Swift Klasse keinerlei Daten oder Logik enthält. Diese steckt ja in unserer C++ Klasse. Wir implementieren lediglich den Aufruf der Funktionen die wir für die Klasse benötigen. Was dann in den Funktionen tatsächlich passiert interessiert uns hier nicht mehr. Was wir uns allerdings merken müssen ist, dass wir den Pointer auf das Objekt in einem UnsafeMutableRawPointer speichern. Dadurch können wir jede der von uns gewrappten Funktionen diesen Pointer wieder übergeben und damit mit diesem Objekt arbeiten. Was noch wichtig ist, ist der Aufruf des Deconstructors in deinit. Immerhin wollen wir keine Speicherlecks erzeugen.

 

Versuchen wir doch ein Mal unser Objekt zu verwenden. Wir verändern also unsere Datei main.swift und erstellen ein Objekt und lassen es uns ausgeben:

Der Output sollte folgendes sein:

Position of Obama:
US-Government
Not US-Government
Program ended with exit code: 0

Ok. Wir wissen nun also dass wir mit unserem C++ Objekt arbeiten. Nun möchte ich aber noch aufführen wie man mit etwas komplexeren Klassenstrukturen umgeht und natürlich eine Logik nutzt die nur in der C++ Bibliothek implementiert ist. Denn bis jetzt haben wir mehr Code geschrieben als hätten wir den Code für zwei Systeme erstellt.

Also erweitern wir unsere wrapper.cpp Datei um folgende Zeilen:

Und den Bridging Header um folgende Zeilen:

 

Jetzt erstellen wir eine Swift Klasse namens Employee:

Hier wird der Kosntruktor ein wenig interessanter, denn wir kommen hier zu dem Punkt an dem wir klären wie wir C++ Klassen untereinander austauschen. Und zwar über den in der Swift Klasse gespeicherten Pointer. Das war es dann aber auch.

In getBossInfo lesen wir nun die Info aus der C++ Klasse aus und nutzen hier die Logik die in der C++ Klasse steckt.

Und letztendlich vergessen wir hier wieder nicht den Speicher wieder freizugeben.

 

Jetzt können wir auch diese Klasse nutzen:

Der Code gibt folgendes aus:

My Boss is:
Barack Obama (55) Abteilung: US-Government
Programm ended with exit code: 0

Und wir sehen hier wieder, dass wir Code verwenden der in C++ implementiert wurde.

 

Doch wie ist das nun mit Vererbung?

Auch vererbende Hierarchien können wir schön darstellen und haben damit sogar einigen Code sparen. Nun versuchen wir also die Klasse Person in Swift einzuführen und als Basisklasse der beiden anderen zu verwenden. Das alles Verbunden mit dem C++ Kontext. Wir erweitern also erst einmal unseren Wrapper.

 

Die nun hinzugefügten Funktionen müssen wir nun wieder über den Bridging Header in Swift bekannt machen:

 

Nun kommt die Klasse Person in Swift:

 

Dem Pointer habe ich nun einen etwas globaleren Namen gegeben. Das hat damit zu tun dass wir diese Konstante auch in den abgeleiteten Klassen verwenden werden.

Nun haben wir einige wichtige Änderungen in unseren ableitenden Klassen.

  1. wir dürfen unseren Pointer nur ein mal durch delete schicken. Andernfalls würden wir beim zweiten Mal versuchen einen Speicher freizugeben der nicht reserviert ist, was zu einem Laufzeitfehler führt. Das könnten wir auch umgehen indem wir den Pointer nach delete sicher auf NULL setzen. Und vor jedem delete überprüfen ob der Pointer ungleich NULL ist.
  2. Wir müssen den Pointer in der Klasse löschen und nur noch mit dem Pointer der Basisklasse arbeiten.
  3. Unsere Inits müssen wir umbauen, sodass die den Pointer in der Basisklasse über den dafür vorgesehenen Init setzen.

Hier also unsere abgewandelten Employee und Boss Klassen:

 

Wie ihr sehen könnt sind alle deinits entfernt. Gleichzeitig übergebe ich den Pointer an den dafür vorgesehenen Init in der Basisklasse. Der ein oder andere mag sich jetzt fragen: “Aber wir rufen doch dann nie die Funtkionen boss_deconstructor und employee_deconstructor auf?” Das ist richtig, aber nicht falsch. Da Pointer ja polymorph sind, können wir über den Pointer auf eine Person, hinter dem aber ein Boss steckt, welcher ja eine Person ist, durchaus den Speicher freigeben.

 

Ich hoffe dieses Tutorial hilft jemandem. Ich würde mich über Kommentare mit Verbesserungsvorschlägen und Kritik freuen!

 

Die beiden Projekte gibt es hier zum Download oder in meinem GitHub Repository:

12 Antworten zu “C++ Bibliothek in Swift (os x und iOS) verwenden. C++ to Swift Class Wrapping”

  1. EdgarCrelo sagt:

    Hei?e Girls fur Sex in deiner Stadt warten auf dich, mach mit: http://nah.uy/TbJdxO

  2. Trevorgep sagt:

    Das beste Geld online ab 3000 $ pro Woche: http://www.1lnk.net//bestprofitsystem81662

  3. Williebib sagt:

    Hot women in your city crave sex: http://ralive.de/hotnakedwomans98046

  4. Larrytub sagt:

    HeiГџe Frauen fГјr guten Sex jeden Tag: http://utiny.ml/bestadultdating86035

  5. DustinBix sagt:

    Fick heute Nacht saftige Frau in deiner Stadt: http://www.short4free.us/bestadultdating99611

  6. LannyTom sagt:

    How to Turn $3,000 into $128,000: https://e13.co/3000daylyprofit11829

  7. JamesSeree sagt:

    Find a hot woman for good sex in your town: http://perkele.ovh/bestadultdating2837486338

  8. Andrewrax sagt:

    Find a hot woman for good sex in your town: http://goto.iamaws.com/99933730

  9. TracyFurse sagt:

    Hot women for sex in your town: https://atho.me/3L2E

  10. RamonCah sagt:

    Fick heute Nacht saftige Frau in deiner Stadt: http://www.short4free.us/bestsexofferinyourcity72345

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.