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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#ifndef SWIFTCPPTUT_PERSON_H #define SWIFTCPPTUT_PERSON_H #include <string> using namespace std; class Person { private: string name; int age; public: Person(const string &name, int age); Person(); const string &getName() const; void setName(const string &name); int getAge() const; void setAge(int age); }; #endif //SWIFTCPPTUT_PERSON_H |
Person.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include "Person.h" Person::Person(const string &name, int age) : name(name), age(age) {} Person::Person() : name("John Doe"), age(29) {} const string &Person::getName() const { return name; } void Person::setName(const string &name) { Person::name = name; } int Person::getAge() const { return age; } void Person::setAge(int age) { Person::age = age; } |
Boss.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#ifndef SWIFTCPPTUT_TEACHER_H #define SWIFTCPPTUT_TEACHER_H #include "Person.h" class Boss : public Person { private: std::string department; public: Boss(const string &name, int age, const string &department); const string &getDepartment() const; void setDepartment(const string &department); }; #endif //SWIFTCPPTUT_TEACHER_H |
Boss.cpp
1 2 3 4 5 6 7 8 9 10 11 |
#include "Boss.h" Boss::Boss(const string &name, int age, const string &department) : Person(name, age), department(department) {} const string &Boss::getDepartment() const { return department; } void Boss::setDepartment(const string &department) { Boss::department = department; } |
Employee.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#ifndef SWIFTCPPTUT_STUDENT_H #define SWIFTCPPTUT_STUDENT_H #include "Person.h" #include "Boss.h" using namespace std; class Employee: public Person { private: Boss *boss; public: Employee(const string &name, int age, Boss *boss); string get_boss_infos() const; }; #endif //SWIFTCPPTUT_STUDENT_H |
Employee.cpp
1 2 3 4 5 6 7 |
#include "Employee.h" Employee::Employee(const string &name, int age, Boss *boss) : Person(name, age), boss(boss) {} string Employee::get_boss_infos() const { return this->boss->getName() + "(" + to_string(this->boss->getAge()) + ") Abteilung: " + this->boss->getDepartment(); } |
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.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <stdio.h> #include "Employee.h" // Boss: extern "C" void * boss_init(const char *name, const int age, const char *department) { Boss *boss = new Boss(string(name), age, string(department)); return boss; } extern "C" void boss_deconstructor(const void *const boss) { const Boss *const b = (Boss *) boss; delete b; } extern "C" void boss_setDepartment(void *const boss, const char *department) { Boss *const b = (Boss *) boss; b->setDepartment(string(department)); } extern "C" const char * boss_getDepartment(const void *const boss) { const Boss *const b = (Boss *) boss; return b->getDepartment().c_str(); } |
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 mitnew 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:
1 2 3 4 5 6 7 |
#include <stdio.h> // Boss: void * boss_init(const char *name, const int age, const char *department); void boss_deconstructor(const void *const boss); void boss_setDepartment(void *const boss, const char *department); const char * boss_getDepartment(const void *const boss); |
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.
1 2 3 4 5 6 7 |
import Foundation public extension String { var cCharArray: UnsafeMutablePointer<Int8> { return UnsafeMutablePointer<Int8>(mutating: (self as NSString).utf8String!) } } |
Nun zur Klasse Boss:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Boss: NSObject { public let cpp_boss_pointer: UnsafeMutableRawPointer public var department: String { get { return String(cString: boss_getDepartment(self.cpp_boss_pointer)) } set { boss_setDepartment(self.cpp_boss_pointer, department.cCharArray) } } init(name: String, age: Int, department: String) { self.cpp_boss_pointer = boss_init(name.cCharArray, Int32(age), department.cCharArray) super.init() } deinit { boss_deconstructor(self.cpp_boss_pointer) } } |
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:
1 2 3 4 5 6 7 8 9 10 11 |
import Foundation print ("Positions of Obama:") let boss = Boss(name: "Barack Obama", age: 55, department: "US-Government") print(boss.department) boss.department = "Not US-Government" print(boss.department) |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Employee: extern "C" void * employee_init(const char *name, const int age, const void *boss) { Employee *emp = new Employee(string(name), age, (Boss *) boss); return emp; } extern "C" void employee_deconstructor(const void *const employee) { const Employee *const e = (Employee *) employee; delete e; } extern "C" const char * employee_get_boss_info(const void *const employee) { const Employee *const e = (Employee *) employee; return e->get_boss_infos().c_str(); } |
Und den Bridging Header um folgende Zeilen:
1 2 3 4 |
// Employee: void * employee_init(const char *name, const int age, const void *boss); void employee_deconstructor(const void *const employee); const char * employee_get_boss_info(const void *const employee); |
Jetzt erstellen wir eine Swift Klasse namens Employee:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Employee: NSObject { private let cpp_employee_pointer: UnsafeMutableRawPointer init(name: String, age: Int, boss: Boss) { self.cpp_employee_pointer = employee_init(name.cCharArray, Int32(age), boss.cpp_boss_pointer) super.init() } func getBossInfo() -> String { return String(cString: employee_get_boss_info(self.cpp_employee_pointer)) } deinit { employee_deconstructor(self.cpp_employee_pointer) } } |
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:
1 2 3 4 5 6 7 |
print ("My Boss is:") let boss = Boss(name: "Barack Obama", age: 55, department: "US-Government") let employee = Employee(name: "John Doe", age: 29, boss: boss) print (employee.getBossInfo()) |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// Person: extern "C" void * person_init(const char *name, const int age) { Person *p = new Person(string(name), age); return p; } extern "C" void person_deconstructor(const void *const person) { const Person *const p = (Person *) person; delete p; } extern "C" const char * person_getName(const void *const person) { const Person *const p = (Person *) person; return p->getName().c_str(); } extern "C" void person_setName(void *const person, const char *const name) { Person *const p = (Person *) person; p->setName(string(name)); } extern "C" const int person_getAge(const void *const person) { const Person *const p = (Person *) person; return p->getAge(); } extern "C" void person_setAge(void *const person, const int age) { Person *const p = (Person *) person; p->setAge(age); } |
Die nun hinzugefügten Funktionen müssen wir nun wieder über den Bridging Header in Swift bekannt machen:
1 2 3 4 5 6 7 |
// Person: void * person_init(const char *name, const int age); void person_deconstructor(const void *const person); const char * person_getName(const void *const person); void person_setName(void *const person, const char *const name); const int person_getAge(const void *const person); void person_setAge(void *const person, const int age); |
Nun kommt die Klasse Person in Swift:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import Cocoa class Person: NSObject { public let cpp_pointer: UnsafeMutableRawPointer public var name: String { get { return String(cString: person_getName(self.cpp_pointer)) } set { person_setName(self.cpp_pointer, newValue.cCharArray) } } public var age: Int { get { return Int(person_getAge(self.cpp_pointer)) } set { person_setAge(self.cpp_pointer, Int32(newValue)) } } init(name: String, age: Int) { self.cpp_pointer = person_init(name.cCharArray, Int32(age)); } init(withPointer pointer: UnsafeMutableRawPointer) { self.cpp_pointer = pointer super.init() } deinit { person_deconstructor(self.cpp_pointer) } } |
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.
- 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. - Wir müssen den Pointer in der Klasse löschen und nur noch mit dem Pointer der Basisklasse arbeiten.
- 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:
1 2 3 4 5 6 7 8 9 10 11 12 |
import Cocoa class Employee: Person { init(name: String, age: Int, boss: Boss) { super.init(withPointer: employee_init(name.cCharArray, Int32(age), boss.cpp_pointer)) } func getBossInfo() -> String { return String(cString: employee_get_boss_info(self.cpp_pointer)) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import Cocoa class Boss: Person { public var department: String { get { return String(cString: boss_getDepartment(self.cpp_pointer)) } set { boss_setDepartment(self.cpp_pointer, department.cCharArray) } } init(name: String, age: Int, department: String) { super.init(withPointer: boss_init(name.cCharArray, Int32(age), department.cCharArray)) } } |
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:

Hei?e Girls fur Sex in deiner Stadt warten auf dich, mach mit: http://nah.uy/TbJdxO
Das beste Geld online ab 3000 $ pro Woche: http://www.1lnk.net//bestprofitsystem81662
Hot women in your city crave sex: http://ralive.de/hotnakedwomans98046
HeiГџe Frauen fГјr guten Sex jeden Tag: http://utiny.ml/bestadultdating86035
Fick heute Nacht saftige Frau in deiner Stadt: http://www.short4free.us/bestadultdating99611
How to Turn $3,000 into $128,000: https://e13.co/3000daylyprofit11829
HeiГџe und saftige Frauen in Ihrer Stadt wollen Sex: http://ads.boylesports.com/redirect.aspx?pid=28721&bid=1487&redirectURL=http%3A%2F%2Finstameet-match33.com%2F%3Fu%3Du348mwe%26o%3D6hle3ul
Find a hot woman for good sex in your town: http://perkele.ovh/bestadultdating2837486338
Find a hot woman for good sex in your town: http://goto.iamaws.com/99933730
Hot women for sex in your town: https://atho.me/3L2E
Fick heute Nacht saftige Frau in deiner Stadt: http://www.short4free.us/bestsexofferinyourcity72345
Fuck tonight juicy woman in your town: http://trendclub.ru/redirect?url=https://xrjjr.lovenights.net/c/da57dc555e50572d?s1=17496&s2=102105&j1=1&j3=1