Interakcja z klasami C ++ w języku Swift

86

Posiadam pokaźną bibliotekę zajęć napisanych w C ++. Próbuję je wykorzystać przez jakiś rodzaj mostu w Swift, zamiast przepisać je jako kod Swift. Podstawową motywacją jest to, że kod C ++ reprezentuje podstawową bibliotekę używaną na wielu platformach. W rzeczywistości tworzę interfejs użytkownika oparty na Swift, aby umożliwić działanie podstawowych funkcji w systemie OS X.

Pojawiają się inne pytania: „Jak wywołać funkcję C ++ z języka Swift”. To nie jest moje pytanie. Aby przejść do funkcji C ++, poniższe działa dobrze:

Zdefiniuj nagłówek mostkujący przez „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 */

Szybki kod może teraz bezpośrednio wywoływać funkcje

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

Moje pytanie jest bardziej szczegółowe. Jak mogę utworzyć instancję i manipulować klasą C ++ z poziomu Swift? Nie mogę znaleźć niczego opublikowanego na ten temat.

David Hoelzer
źródło
8
Osobiście uciekłem się do napisania zwykłych plików opakowujących Objective-C ++, które ujawniają klasę Objective-C, która odtwarza wszystkie odpowiednie wywołania C ++ i po prostu przekazuje je do utrzymywanej instancji klasy C ++. W moim przypadku liczba klas i wywołań C ++ jest niewielka, więc nie jest to szczególnie pracochłonne. Ale powstrzymam się od propagowania tego jako odpowiedzi w nadziei, że ktoś wymyśli coś lepszego.
Tommy,
1
Cóż, to jest coś ... Poczekajmy i zobaczmy (i miejmy nadzieję).
David Hoelzer,
Otrzymałem sugestię za pośrednictwem IRC, aby napisać klasę opakowującą Swift, która utrzymuje void wskaźnik do rzeczywistego obiektu C ++ i ujawnia wymagane metody, które w rzeczywistości są po prostu przekazywane przez most C i wskaźnik do obiektu.
David Hoelzer,
4
Jako aktualizacja tego, Swift 3.0 jest teraz dostępny i pomimo wcześniejszych obietnic, współdziałanie C ++ jest teraz oznaczone jako „Poza zakresem”.
David Hoelzer,

Odpowiedzi:

61

Opracowałem doskonale wykonalną odpowiedź. Stopień czystości zależy wyłącznie od tego, ile pracy chcesz wykonać.

Najpierw weź swoją klasę C ++ i utwórz funkcje „wrappera” języka C, aby z nią współpracować. Na przykład, jeśli mamy tę klasę 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);
};

Następnie implementujemy te funkcje 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;
}

Nagłówek mostu zawiera wtedy:

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

W Swift możemy teraz utworzyć instancję obiektu i wchodzić z nim w interakcję w następujący sposób:

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!

Tak więc, jak widać, rozwiązaniem (które w rzeczywistości jest raczej proste) jest utworzenie opakowań, które utworzą instancję obiektu i zwrócą wskaźnik do tego obiektu. Można to następnie przekazać z powrotem do funkcji opakowujących, które mogą z łatwością traktować go jako obiekt zgodny z tą klasą i wywoływać funkcje składowe.

Czystsze

Chociaż jest to fantastyczny początek i udowadnia, że ​​użycie istniejących klas C ++ z trywialnym mostkiem jest całkowicie wykonalne, może być nawet czystsze.

Oczyszczenie tego oznaczałoby po prostu usunięcie UnsafeMutablePointer<Void>kodu ze środka naszego kodu Swift i zamknięcie go w klasie Swift. Zasadniczo używamy tych samych funkcji opakowujących C / C ++, ale łączymy je z klasą Swift. Klasa Swift przechowuje odwołanie do obiektu i po prostu przekazuje wszystkie wywołania metod i atrybutów przez most do obiektu C ++!

Po wykonaniu tej czynności cały kod mostkujący jest całkowicie zamknięty w klasie Swift. Mimo że nadal używamy mostka C, efektywnie używamy obiektów C ++ w sposób przejrzysty bez konieczności ponownego kodowania ich w Objective-C lub Objective-C ++.

David Hoelzer
źródło
12
Jest tu kilka ważnych pominiętych problemów. Po pierwsze, destruktor nigdy nie jest wywoływany, a obiekt wycieka. Po drugie, wyjątki mogą powodować nieprzyjemne problemy.
Michael Anderson
2
Omówiłem kilka problemów w mojej odpowiedzi na to pytanie stackoverflow.com/questions/2045774/…
Michael Anderson
2
@DavidHoelzer, chciałem tylko podkreślić ten pomysł, ponieważ niektórzy programiści będą próbowali opakować całą bibliotekę C ++ i używać jej tak, jak została napisana w języku Swift. Dałeś świetny przykład tego, jak „utworzyć instancję i manipulować klasą C ++ z poziomu Swift”! Chciałem tylko dodać, że tej techniki należy używać ostrożnie! Dziękuję za przykład!
Nicolai Nita
1
Nie myśl, że to jest „idealne”. Myślę, że to prawie to samo, co piszesz opakowanie Objective-C, a następnie używasz go w języku Swift.
Bagusflyer
1
@Bagusflyer na pewno ... poza tym, że to wcale nie jest opakowanie na obiektyw C.
David Hoelzer
11

Swift nie ma obecnie współdziałania z C ++. Jest to cel długoterminowy, ale jest mało prawdopodobne, aby pojawił się w najbliższej przyszłości.

Catfish_Man
źródło
25
Nazywanie „używaj opakowań C” w formie „współdziałania C ++” dość
mocno wydłuża
6
Być może, ale użycie ich w celu umożliwienia utworzenia wystąpienia klasy C ++, a następnie wywołania metod, czyni ją użyteczną i spełnia pytanie.
David Hoelzer,
2
W rzeczywistości nie jest to już cel długoterminowy, ale zamiast tego jest oznaczony jako „Poza zakresem” w mapie drogowej.
David Hoelzer
4
używanie c-wrapperów jest standardowym podejściem dla prawie wszystkich połączeń między C ++ z dowolnym językiem, Pythonem, Javą itp.
Andrew Paul Simmons
8

Oprócz własnego rozwiązania istnieje inny sposób, aby to zrobić. Możesz wywołać lub bezpośrednio napisać kod C ++ w Objective-C ++.

Możesz więc utworzyć opakowanie obiektywnego C ++ na wierzchu kodu C ++ i stworzyć odpowiedni interfejs.

Następnie wywołaj kod Objective-C ++ z kodu Swift. Aby móc napisać kod obiektywnego C ++, może być konieczna zmiana rozszerzenia pliku z .m na .mm

Nie zapomnij zwolnić pamięci przydzielonej przez obiekty C ++, gdy jest to odpowiednie.

Ahmed
źródło
6

Jak wspomniano w innej odpowiedzi, używanie ObjC ++ do interakcji jest znacznie łatwiejsze. Po prostu nazwij swoje pliki .mm zamiast .m i xcode / clang, daje ci dostęp do c ++ w tym pliku.

Zauważ, że ObjC ++ nie obsługuje dziedziczenia w C ++. Jeśli chcesz podklasować klasę c ++ w ObjC ++, nie możesz. Będziesz musiał napisać podklasę w C ++ i owinąć ją wokół klasy ObjC ++.

Następnie użyj nagłówka mostkowania, którego normalnie używałbyś do wywołania objc ze swift.

nnrales
źródło
2
Być może przegapiłeś punkt. Interfejsy są wymagane, ponieważ mamy bardzo dużą, wieloplatformową aplikację C ++, którą teraz obsługujemy również w systemie OS X. Obiektywny C ++ naprawdę nie jest opcją dla całego projektu.
David Hoelzer,
Nie jestem pewien, czy cię rozumiem. Potrzebujesz tylko ObjC ++, gdy chcesz, aby wywołanie przechodziło między swift a c ++ lub odwrotnie. Tylko granice muszą być zapisane w objc ++, a nie w całym projekcie.
nnrales
1
Tak, przejrzałem twoje pytanie. Wydaje się, że chcesz bezpośrednio manipulować C ++ z poziomu swift. Nie wiem, jak to zrobić.
nnrales
1
To właśnie osiąga moja opublikowana odpowiedź. Funkcje C pozwalają na przekazywanie i wysyłanie obiektów jako dowolnych porcji pamięci oraz wywoływanie metod po obu stronach.
David Hoelzer,
1
@DavidHoelzer Wydaje się, że to fajne, ale czy nie komplikujesz w ten sposób kodu? Myślę, że to subiektywne. Opakowanie ObjC ++ wydaje mi się czystsze.
nnrales
5

Możesz użyć Scapix Language Bridge, aby automatycznie połączyć C ++ ze Swift (między innymi). Kod mostu generowany automatycznie w locie bezpośrednio z plików nagłówkowych C ++. Oto przykład :

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

Szybki:

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

        contact.sendMessage("Hello", friend)
        contact.addTags(["a","b","c"])
        contact.addFriends([friend])
    }
}
Boris Rasin
źródło
Ciekawy. Wygląda na to, że wydałeś to po raz pierwszy w marcu 2019 r.
David Hoelzer
@ boris-rasin W tym przykładzie nie mogę znaleźć bezpośredniego C ++ do szybkiego mostu. Czy masz tę funkcję w planach?
sage444
2
Tak, w tej chwili nie ma bezpośredniego mostu C ++ do Swift. Ale mostek C ++ do ObjC działa doskonale dla Swift (przez mostek ObjC firmy Apple do Swift). Pracuję teraz nad mostem bezpośrednim (w niektórych przypadkach zapewni on lepszą wydajność).
Boris Rasin
1
O tak, masz całkowitą rację, ale w nagim, szybkim projekcie na Linuksie tak nie jest. Czekamy na Twoją realizację.
sage444