Interagire con le classi C ++ di Swift


89

Ho una significativa libreria di classi scritte in C ++. Sto cercando di utilizzarli attraverso un qualche tipo di bridge all'interno di Swift piuttosto che riscriverli come codice Swift. La motivazione principale è che il codice C ++ rappresenta una libreria di base utilizzata su più piattaforme. In effetti, sto solo creando un'interfaccia utente basata su Swift per consentire alle funzionalità principali di funzionare con OS X.

Ci sono altre domande che chiedono: "Come posso chiamare una funzione C ++ da Swift". Questa non è la mia domanda. Per collegare una funzione C ++, funziona bene quanto segue:

Definisci un'intestazione di bridging tramite "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 */

Il codice Swift ora può chiamare direttamente le funzioni

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

La mia domanda è più specifica. Come posso creare un'istanza e manipolare una classe C ++ da Swift? Non riesco a trovare nulla di pubblicato su questo.


9
Personalmente ho fatto ricorso alla scrittura di semplici file wrapper Objective-C ++ che espongono una classe Objective-C che riproduce tutte le chiamate C ++ pertinenti e le inoltra semplicemente a un'istanza detenuta della classe C ++. Nel mio caso il numero di classi e chiamate C ++ è piccolo, quindi non è particolarmente laborioso. Ma mi trattengo dal sostenere questo come una risposta nella speranza che qualcuno abbia escogitato qualcosa di meglio.
Tommy

1
Bene, è qualcosa ... Aspettiamo e vediamo (e speriamo).
David Hoelzer

Ho ricevuto un suggerimento tramite IRC per scrivere una classe wrapper Swift che mantenga un puntatore void all'oggetto C ++ effettivo ed esponga i metodi richiesti che sono, in effetti, passati attraverso il bridge C e il puntatore all'oggetto.
David Hoelzer

4
Come aggiornamento a questo, Swift 3.0 è ora disponibile e, nonostante le promesse precedenti, l'interoperabilità C ++ è ora contrassegnata come "Fuori ambito".
David Hoelzer

Risposte:


63

Ho elaborato una risposta perfettamente gestibile. Quanto pulito vorresti che fosse questo dipende interamente da quanto lavoro sei disposto a fare.

Per prima cosa, prendi la tua classe C ++ e crea funzioni C "wrapper" per interfacciarla con essa. Ad esempio, se abbiamo questa classe C ++:

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);
};

Quindi implementiamo queste funzioni C ++:

#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;
}

L'intestazione del bridge contiene quindi:

#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 */

Da Swift, ora possiamo istanziare l'oggetto e interagire con esso in questo modo:

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!

Quindi, come puoi vedere, la soluzione (che in realtà è piuttosto semplice) è creare wrapper che istanzeranno un oggetto e restituiranno un puntatore a quell'oggetto. Questo può quindi essere ritrasmesso alle funzioni wrapper che possono facilmente trattarlo come un oggetto conforme a quella classe e chiamare le funzioni membro.

Rendendolo più pulito

Anche se questo è un inizio fantastico e dimostra che è completamente fattibile utilizzare le classi C ++ esistenti con un ponte banale, può essere ancora più pulito.

Ripulirlo significherebbe semplicemente che rimuoviamo UnsafeMutablePointer<Void>dal centro del nostro codice Swift e lo incapsuliamo in una classe Swift. Essenzialmente, usiamo le stesse funzioni wrapper C / C ++ ma le interfacciamo con una classe Swift. La classe Swift mantiene il riferimento all'oggetto e essenzialmente passa tutte le chiamate di riferimento ai metodi e agli attributi attraverso il bridge all'oggetto C ++!

Fatto ciò, tutto il codice bridging è completamente incapsulato nella classe Swift. Anche se stiamo ancora utilizzando un bridge C, stiamo effettivamente utilizzando oggetti C ++ in modo trasparente senza dover ricorrere a ricodificarli in Objective-C o Objective-C ++.


13
Ci sono un paio di importanti problemi persi qui. Il primo è che il distruttore non viene mai chiamato e l'oggetto perde. La seconda è che le eccezioni potrebbero causare fastidiosi problemi.
Michael Anderson

2
Ho coperto un sacco di problemi nella mia risposta a questa domanda stackoverflow.com/questions/2045774/…
Michael Anderson

2
@DavidHoelzer, volevo solo sottolineare questa idea perché alcuni sviluppatori cercheranno di racchiudere un'intera libreria C ++ e di usarla come è stata scritta in Swift. Hai fornito un ottimo esempio di come "istanziare e manipolare una classe C ++ dall'interno di Swift"! E volevo solo aggiungere che questa tecnica dovrebbe essere usata con cautela! Grazie per il tuo esempio!
Nicolai Nita

1
Non pensare che sia "perfetto". Penso che sia quasi lo stesso che scrivi un wrapper Objective-C e poi lo usi in Swift.
Bagusflyer

1
@Bagusflyer sicuro ... tranne per il fatto che non è affatto un wrapper obiettivo-C.
David Hoelzer

11

Swift non ha attualmente l'interoperabilità C ++. È un obiettivo a lungo termine, ma è molto improbabile che accada nel prossimo futuro.


25
Chiamare "usa wrapper C" una forma di "interoperabilità C ++" sta estendendo un po 'il termine
Catfish_Man

6
Forse, ma usarli per consentire di istanziare una classe C ++ e quindi chiamare metodi su di essa lo rende utile e soddisfa la domanda.
David Hoelzer

2
In effetti, non è più un obiettivo a lungo termine ma, invece, è contrassegnato come "Fuori ambito" nella tabella di marcia.
David Hoelzer

4
l'utilizzo di c-wrapper è l'approccio standard per quasi tutte le interoperabilità C ++ con qualsiasi linguaggio, python, java, ecc.
Andrew Paul Simmons

8

Oltre alla tua soluzione, c'è un altro modo per farlo. Puoi chiamare o scrivere direttamente codice C ++ in Objective-C ++.

Quindi puoi creare un wrapper C ++ obiettivo sopra il tuo codice C ++ e creare un'interfaccia adatta.

Quindi chiama il codice Object-C ++ dal tuo codice swift. Per poter scrivere codice Object-C ++ potrebbe essere necessario rinominare l'estensione del file da .m a .mm

Non dimenticare di rilasciare la memoria allocata dai tuoi oggetti C ++ quando opportuno.


7

Puoi utilizzare Scapix Language Bridge per collegare automaticamente C ++ a Swift (tra le altre lingue). Codice Bridge generato automaticamente al volo direttamente dai file di intestazione C ++. Ecco un esempio :

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);
};

Swift:

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

        contact.sendMessage("Hello", friend)
        contact.addTags(["a","b","c"])
        contact.addFriends([friend])
    }
}

Interessante. Sembra che tu l'abbia pubblicato per la prima volta nel marzo del 2019.
David Hoelzer

@ boris-rasin Non riesco a trovare c ++ diretto a swift bridge nell'esempio. Hai questa funzione nei piani?
sage444

2
Sì, al momento non esiste un bridge diretto da C ++ a Swift. Ma il bridge da C ++ a ObjC funziona perfettamente per Swift (tramite il bridge da ObjC a Swift di Apple). In questo momento sto lavorando al bridge diretto (in alcuni casi fornirà prestazioni migliori).
Boris Rasin

1
Oh sì, hai perfettamente ragione, ma in un progetto semplice e veloce su Linux questo non è il caso. Non vedo l'ora di vedere la tua implementazione.
sage444

6

Come un'altra risposta menzionata, usare ObjC ++ per interagire è molto più semplice. Basta nominare i tuoi file .mm invece di .m e xcode / clang, ti dà accesso a c ++ in quel file.

Nota che ObjC ++ non supporta l'ereditarietà C ++. Se vuoi creare una sottoclasse di una classe C ++ in ObjC ++, non puoi. Dovrai scrivere la sottoclasse in C ++ e avvolgerla attorno a una classe ObjC ++.

Quindi usa l'intestazione di bridging che useresti normalmente per chiamare objc da swift.


2
Potresti aver perso il punto. Le interfacce sono necessarie perché disponiamo di un'applicazione C ++ multipiattaforma molto grande che ora supportiamo anche su OS X. Objective C ++ non è davvero un'opzione per l'intero progetto.
David Hoelzer

Non sono sicuro di capirti. Hai solo bisogno di ObjC ++ quando vuoi che una chiamata passi tra swift e c ++ o viceversa. Solo i confini devono essere scritti in objc ++, non l'intero progetto.
nnrales

1
Sì, ho esaminato la tua domanda. Sembra che tu stia cercando di manipolare direttamente C ++ da swift. Non so come farlo.
nnrales

1
Questo è ciò che la mia risposta pubblicata realizza. Le funzioni C consentono di passare gli oggetti dentro e fuori come blocchi arbitrari di memoria e di chiamare metodi su entrambi i lati.
David Hoelzer

1
@DavidHoelzer Pensa che sia bello, ma non stai rendendo il codice più complicato in questo modo? Immagino sia soggettivo. Il wrapper ObjC ++, mi sembra più pulito.
nnrales
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.