Interaktion mit C ++ - Klassen von Swift

86

Ich habe eine bedeutende Bibliothek von Klassen in C ++ geschrieben. Ich versuche, sie über eine Art Brücke in Swift zu verwenden, anstatt sie als Swift-Code umzuschreiben. Die Hauptmotivation ist, dass der C ++ - Code eine Kernbibliothek darstellt, die auf mehreren Plattformen verwendet wird. Eigentlich erstelle ich nur eine Swift-basierte Benutzeroberfläche, damit die Kernfunktionalität unter OS X funktioniert.

Es gibt andere Fragen: "Wie rufe ich eine C ++ - Funktion von Swift aus auf?" Das ist nicht meine Frage. Um eine Brücke zu einer C ++ - Funktion zu schlagen, funktioniert Folgendes einwandfrei:

Definieren Sie einen Bridging-Header durch "C".

#ifndef ImageReader_hpp
#define ImageReader_hpp

#ifdef __cplusplus
extern "C" {
#endif

    const char *hexdump(char *filename);
    const char *imageType(char *filename);

#ifdef __cplusplus
}
#endif

#endif /* ImageReader_hpp */

Swift-Code kann jetzt Funktionen direkt aufrufen

let type = String.fromCString(imageType(filename))
let dump = String.fromCString(hexdump(filename))

Meine Frage ist spezifischer. Wie kann ich eine C ++ - Klasse in Swift instanziieren und bearbeiten? Ich kann anscheinend nichts dazu finden.

David Hoelzer
quelle
8
Ich persönlich habe darauf zurückgegriffen, einfache Objective-C ++ - Wrapper-Dateien zu schreiben, die eine Objective-C-Klasse verfügbar machen, die alle relevanten C ++ - Aufrufe reproduziert und sie einfach an eine gehaltene Instanz der C ++ - Klasse weiterleitet. In meinem Fall ist die Anzahl der C ++ - Klassen und -Aufrufe gering und daher nicht besonders arbeitsintensiv. Aber ich werde mich zurückhalten, um dies als Antwort zu befürworten, in der Hoffnung, dass sich jemand etwas Besseres einfallen lässt.
Tommy
1
Nun, es ist etwas ... Lass uns abwarten (und hoffen).
David Hoelzer
Ich habe über IRC einen Vorschlag erhalten, eine Swift-Wrapper-Klasse zu schreiben, die einen ungültigen Zeiger auf das eigentliche C ++ - Objekt verwaltet und die erforderlichen Methoden verfügbar macht, die effektiv nur über die C-Brücke und den Zeiger auf das Objekt durchlaufen werden.
David Hoelzer
4
Als Update dazu ist Swift 3.0 jetzt verfügbar und trotz früherer Versprechungen wird die C ++ - Interoperabilität jetzt als "außerhalb des Gültigkeitsbereichs" markiert.
David Hoelzer

Antworten:

61

Ich habe eine perfekt handhabbare Antwort ausgearbeitet. Wie sauber dies sein soll, hängt ganz davon ab, wie viel Arbeit Sie bereit sind.

Nehmen Sie zuerst Ihre C ++ - Klasse und erstellen Sie C-Wrapper-Funktionen, um eine Schnittstelle zu ihr herzustellen. Wenn wir zum Beispiel diese C ++ - Klasse haben:

class MBR {
    std::string filename;

public:
    MBR (std::string filename);
    const char *hexdump();
    const char *imageType();
    const char *bootCode();
    const char *partitions();
private:
    bool readFile(unsigned char *buffer, const unsigned int length);
};

Wir implementieren dann diese C ++ - Funktionen:

#include "MBR.hpp"

using namespace std;
const void * initialize(char *filename)
{
    MBR *mbr = new MBR(filename);

    return (void *)mbr;
}

const char *hexdump(const void *object)
{
    MBR *mbr;
    static char retval[2048];

    mbr = (MBR *)object;
    strcpy(retval, mbr -> hexdump());
    return retval;
}

const char *imageType(const void *object)
{
    MBR *mbr;
    static char retval[256];

    mbr = (MBR *)object;
    strcpy(retval, mbr -> imageType());
    return retval;
}

Der Bridge-Header enthält dann:

#ifndef ImageReader_hpp
#define ImageReader_hpp

#ifdef __cplusplus
extern "C" {
#endif

    const void *initialize(char *filename);
    const char *hexdump(const void *object);
    const char *imageType(const void *object);

#ifdef __cplusplus
}
#endif

#endif /* ImageReader_hpp */

Von Swift aus können wir jetzt das Objekt instanziieren und wie folgt damit interagieren:

let cppObject = UnsafeMutablePointer<Void>(initialize(filename))
let type = String.fromCString(imageType(cppObject))
let dump = String.fromCString(hexdump(cppObject))                
self.imageTypeLabel.stringValue = type!
self.dumpDisplay.stringValue = dump!

Wie Sie sehen können, besteht die Lösung (die eigentlich ziemlich einfach ist) darin, Wrapper zu erstellen, die ein Objekt instanziieren und einen Zeiger auf dieses Objekt zurückgeben. Dies kann dann an die Wrapper-Funktionen zurückgegeben werden, die es leicht als ein Objekt behandeln können, das dieser Klasse entspricht, und die Elementfunktionen aufrufen können.

Sauberer machen

Dies ist zwar ein fantastischer Start und beweist, dass es durchaus machbar ist, vorhandene C ++ - Klassen mit einer trivialen Brücke zu verwenden, aber es kann noch sauberer sein.

Dies zu bereinigen würde einfach bedeuten, dass wir den UnsafeMutablePointer<Void>Code aus der Mitte unseres Swift-Codes entfernen und ihn in eine Swift-Klasse einkapseln. Im Wesentlichen verwenden wir dieselben C / C ++ - Wrapperfunktionen, verbinden sie jedoch mit einer Swift-Klasse. Die Swift-Klasse verwaltet die Objektreferenz und übergibt im Wesentlichen alle Methoden- und Attributreferenzaufrufe über die Bridge an das C ++ - Objekt!

Danach ist der gesamte Bridging-Code vollständig in der Swift-Klasse gekapselt. Obwohl wir immer noch eine C-Brücke verwenden, verwenden wir C ++ - Objekte effektiv transparent, ohne sie in Objective-C oder Objective-C ++ neu codieren zu müssen.

David Hoelzer
quelle
12
Hier gibt es einige wichtige verpasste Probleme. Das erste ist, dass der Destruktor niemals aufgerufen wird und das Objekt durchgesickert ist. Das zweite ist, dass Ausnahmen einige böse Probleme verursachen können.
Michael Anderson
2
Ich habe eine Reihe von Problemen in meiner Antwort auf diese Frage behandelt. Stackoverflow.com/questions/2045774/…
Michael Anderson
2
@DavidHoelzer, ich wollte diese Idee nur betonen, weil einige Entwickler versuchen werden, eine gesamte C ++ - Bibliothek zu verpacken und sie so zu verwenden, wie sie in Swift geschrieben wurde. Sie haben ein großartiges Beispiel dafür gegeben, wie man "eine C ++ - Klasse in Swift instanziiert und manipuliert"! Und ich wollte nur hinzufügen, dass diese Technik mit Vorsicht angewendet werden sollte! Vielen Dank für Ihr Beispiel!
Nicolai Nita
1
Denken Sie nicht, dass dies "perfekt" ist. Ich denke, es ist fast dasselbe, als würden Sie einen Objective-C-Wrapper schreiben und ihn dann in Swift verwenden.
Bagusflyer
1
@Bagusflyer sicher ... außer dass es überhaupt kein Objective-C-Wrapper ist.
David Hoelzer
11

Swift hat derzeit kein C ++ - Interop. Es ist ein langfristiges Ziel, aber es ist sehr unwahrscheinlich, dass es in naher Zukunft erreicht wird.

Catfish_Man
quelle
25
Das Nennen von "C-Wrapper verwenden", eine Form von "C ++ Interop", erweitert den Begriff ziemlich
Catfish_Man
6
Vielleicht, aber wenn Sie sie verwenden, um eine C ++ - Klasse zu instanziieren und dann Methoden aufzurufen, ist sie nützlich und erfüllt die Frage.
David Hoelzer
2
Tatsächlich ist es kein langfristiges Ziel mehr, sondern wird in der Roadmap als "außerhalb des Geltungsbereichs" gekennzeichnet.
David Hoelzer
4
Die Verwendung von C-Wrappern ist der Standardansatz für fast alle C ++ - Interops in jeder Sprache, Python, Java usw.
Andrew Paul Simmons
8

Neben Ihrer eigenen Lösung gibt es noch eine andere Möglichkeit. Sie können C ++ - Code in Objective-C ++ aufrufen oder direkt schreiben.

Sie können also einen Objective-C ++ - Wrapper über Ihrem C ++ - Code erstellen und eine geeignete Schnittstelle erstellen.

Rufen Sie dann den Objective-C ++ - Code aus Ihrem schnellen Code auf. Um Objective-C ++ - Code schreiben zu können, müssen Sie möglicherweise die Dateierweiterung von .m in .mm umbenennen

Vergessen Sie nicht, bei Bedarf den von Ihren C ++ - Objekten zugewiesenen Speicher freizugeben.

Ahmed
quelle
6

Wie bereits erwähnt, ist die Verwendung von ObjC ++ für die Interaktion viel einfacher. Benennen Sie einfach Ihre Dateien .mm anstelle von .m und xcode / clang, um Zugriff auf c ++ in dieser Datei zu erhalten.

Beachten Sie, dass ObjC ++ die C ++ - Vererbung nicht unterstützt. Wenn Sie eine C ++ - Klasse in ObjC ++ unterordnen möchten, können Sie dies nicht. Sie müssen die Unterklasse in C ++ schreiben und um eine ObjC ++ - Klasse wickeln.

Verwenden Sie dann den Bridging-Header, den Sie normalerweise verwenden würden, um objc von swift aus aufzurufen.

nnrales
quelle
2
Möglicherweise haben Sie den Punkt verpasst. Die Schnittstellen sind erforderlich, da wir eine sehr große plattformübergreifende C ++ - Anwendung haben, die wir jetzt auch unter OS X unterstützen. Ziel C ++ ist wirklich keine Option für das gesamte Projekt.
David Hoelzer
Ich bin mir nicht sicher, ob ich dich verstehe. Sie benötigen nur ObjC ++, wenn ein Aufruf zwischen swift und c ++ oder umgekehrt erfolgen soll. Nur die Grenzen müssen in objc ++ geschrieben werden, nicht das gesamte Projekt.
nnrales
1
Ja, ich habe deine Frage durchgesehen. Sie scheinen zu versuchen, C ++ direkt von schnell zu manipulieren. Ich weiß nicht, wie ich das machen soll.
nnrales
1
Das ist es, was meine gepostete Antwort bewirkt. Mit den C-Funktionen können Sie die Objekte als beliebige Speicherblöcke ein- und ausgeben und Methoden auf beiden Seiten aufrufen.
David Hoelzer
1
@DavidHoelzer Ich finde es cool, aber machst du den Code nicht auf diese Weise komplizierter? Ich denke es ist subjektiv. Der ObjC ++ - Wrapper scheint mir sauberer zu sein.
nnrales
5

Mit Scapix Language Bridge können Sie C ++ (unter anderem) automatisch mit Swift verbinden. Bridge-Code wird automatisch direkt aus C ++ - Header-Dateien generiert. Hier ist ein Beispiel :

C ++:

#include <scapix/bridge/object.h>

class contact : public scapix::bridge::object<contact>
{
public:
    std::string name();
    void send_message(const std::string& msg, std::shared_ptr<contact> from);
    void add_tags(const std::vector<std::string>& tags);
    void add_friends(std::vector<std::shared_ptr<contact>> friends);
};

Schnell:

class ViewController: UIViewController {
    func send(friend: Contact) {
        let c = Contact()

        contact.sendMessage("Hello", friend)
        contact.addTags(["a","b","c"])
        contact.addFriends([friend])
    }
}
Boris Rasin
quelle
Interessant. Sieht so aus, als hätten Sie dies gerade zum ersten Mal im März 2019 veröffentlicht.
David Hoelzer
@ boris-rasin Ich kann im Beispiel kein direktes C ++ für eine schnelle Brücke finden. Haben Sie diese Funktion in Plänen?
sage444
2
Ja, es gibt derzeit kein direktes C ++ zu Swift Bridge. Aber die C ++ zu ObjC-Brücke funktioniert für Swift einwandfrei (über Apples ObjC zu Swift-Brücke). Ich arbeite gerade an der direkten Brücke (sie bietet in einigen Fällen eine bessere Leistung).
Boris Rasin
1
Oh ja, Sie haben vollkommen recht, aber bei einem schnellen Projekt unter Linux ist dies nicht der Fall. Ich freue mich auf Ihre Implementierung.
sage444