2016-02-05 29 views
37

Ho una libreria significativa di classi scritte in C++. Sto cercando di farne uso attraverso 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 che viene utilizzata su più piattaforme. Effettivamente, sto solo creando un'interfaccia utente basata su Swift per consentire alle funzionalità di base di funzionare sotto OS X.Interagire con classi C++ da Swift

Ci sono altre domande che richiedono "Come si chiama una funzione C++ da Swift". Questo è non la mia domanda. Per colmare a una funzione C++, il seguente funziona bene:

definire un'intestazione ponte attraverso "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 */ 

codice Swift può ora chiamare funzioni direttamente

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

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

+4

ho personalmente fatto ricorso alla scrittura di semplici file wrapper Objective-C++ che espongono una classe Objective-C che riproduce tutte le pertinenti C++ chiama e semplicemente li inoltra a un'istanza in attesa della classe C++. Nel mio caso il numero di classi e chiamate C++ è piccolo, quindi non è particolarmente laborioso. Ma continuerò a difenderlo come una risposta nella speranza che qualcuno possa inventarsi qualcosa di meglio. – Tommy

+0

Beh, è ​​qualcosa ... Aspettiamo e vediamo (e speriamo). –

+0

Ho ricevuto un suggerimento via IRC per scrivere una classe wrapper Swift che mantiene un puntatore vuoto all'oggetto C++ e espone i metodi richiesti che sono, in effetti, appena passati attraverso il bridge C e il puntatore all'oggetto. –

risposta

33

Ho elaborato una risposta perfettamente gestibile. La pulizia che vorresti fosse interamente basata su quanto lavoro sei disposto a fare.

Innanzitutto, prendi la classe C++ e crea le funzioni di "wrapper" C per interfacciarlo con esso. 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); 
}; 

Abbiamo quindi implementare 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 ponte comprende 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, possiamo ora 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 potete vedere, la soluzione (che in realtà è piuttosto semplice) è creare wrapper che istanzia un oggetto e restituiscono un puntatore a quell'oggetto. Questo può quindi essere passato di nuovo nelle funzioni wrapper che possono facilmente trattarlo come un oggetto conforme a quella classe e chiamare le funzioni membro.

rendendolo più pulito

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

Pulire questo significa semplicemente rimuovere il codice UnsafeMutablePointer<Void> dal centro del nostro codice Swift e incapsularlo in una classe Swift. In sostanza, utilizziamo le stesse funzioni wrapper C/C++ ma le interfacciamo con una classe Swift. La classe Swift mantiene il riferimento all'oggetto e in pratica passa semplicemente tutto il metodo e attribuisce le chiamate di riferimento attraverso il bridge all'oggetto C++!

Dopo aver fatto questo, tutto il codice di bridging è completamente incapsulato nella classe Swift. Anche se stiamo ancora usando un bridge C, stiamo usando efficacemente oggetti C++ in modo trasparente, senza dover ricorrere a ricodificarli in Objective-C o Objective-C++.

+6

Ci sono un paio di importanti problemi persi qui. Il primo è che il distruttore non viene mai chiamato e l'oggetto è trapelato. Il secondo è che le eccezioni potrebbero causare qualche brutto problema. –

+0

Ho coperto un sacco di problemi nella mia risposta a questa domanda http://stackoverflow.com/questions/2045774/developing-c-wrapper-api-for-object-oriented-c-code/2045860 –

+0

Il wrapping in ObjC++ è il metodo migliore La memoria viene gestita meglio e non è necessario tenere premuto un (vuoto *). Prendi in considerazione l'utilizzo di uno strumento come Djinni per creare le interfacce Obj-C per te. –

8

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

+18

Chiamando "usa wrapper C" una forma di "C++ interop" sta allungando il termine un po ' –

+2

Forse, ma usarli per consentire l'istanziazione di una classe C++ e quindi chiamare i metodi su di esso lo rende utile e soddisfa la domanda. –

+0

In effetti, non è più un obiettivo a lungo termine ma, invece, è contrassegnato come "Fuori campo" nella roadmap. –

4

Oltre alla propria soluzione, esiste un altro modo per farlo. Puoi chiamare o scrivere direttamente codice C++ in ogg-C++.

Quindi, se si crea un involucro Objective-C++ in cima al vostro codice C++ e creare un adeguato un'interfaccia

e chiamare obiettivo a codice C++ dal codice swift. Per essere in grado di scrivere Objective-C++ di codice potrebbe essere necessario rinominare estensione del file da .m a .MM

Non dimenticare di liberare memoria allocata da tuoi oggetti C++ quando adatto.

1

Come accennato un'altra risposta, utilizzando objC++ di interagire è molto più facile. Basta dare un nome ai file .mm invece di .m e xcode/clang, ti dà accesso a C++ in quel file.

Nota che objC++ non supporta C++ eredità. Voglio sottoclassi una classe C++ in ObjC++, non puoi. Dovrai scrivere la sottoclasse in C++ e avvolgerla attorno ad una classe ObjC++.

quindi utilizzare l'intestazione bridging che si usa normalmente per chiamare objc da SWIFT.

Problemi correlati