Взаємодія з класами C ++ від Swift


86

У мене є значна бібліотека класів, написаних на C ++. Я намагаюся використовувати їх через якийсь міст у Swift, а не переписувати їх як код Swift. Основна мотивація полягає в тому, що код С ++ являє собою основну бібліотеку, яка використовується на декількох платформах. Фактично, я просто створюю інтерфейс на основі Swift, щоб основна функціональність могла працювати під OS X.

Є й інші запитання: "Як мені викликати функцію C ++ із Swift." Це не моє запитання. Для переходу до функції 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 тепер може викликати функції безпосередньо

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

Моє питання більш конкретне. Як я можу створити екземпляр та маніпулювати класом C ++ із Swift? Здається, я не можу знайти нічого опублікованого з цього приводу.


8
Я особисто вдався до написання простих файлів обгортки Objective-C ++, які виставляють клас Objective-C, який відтворює всі відповідні виклики C ++ і просто пересилає їх до утримуваного екземпляра класу C ++. У моєму випадку кількість класів та дзвінків на C ++ невелика, тому це не особливо трудомістко. Але я затримаюся, обстоюючи це як відповідь, сподіваючись, що хтось придумає щось краще.
Томмі

1
Ну, це щось ... Зачекаємо і побачимо (і сподіваємось).
Девід Хольцер

Я отримав пропозицію через IRC написати клас обгортки Swift, який підтримує покажчик void на фактичний об'єкт C ++ і виставляє необхідні методи, які фактично щойно передані через мост C та вказівник на об'єкт.
Девід Хольцер

4
Як оновлення до цього, Swift 3.0 зараз вийшов, і, незважаючи на попередні обіцянки, сумісність C ++ тепер позначена як "поза сферою дії".
Девід Хольцер

Відповіді:


61

Я розробив цілком керовану відповідь. Наскільки чистою ви хотіли б, щоб це було, цілком залежить від того, скільки роботи ви готові зробити.

По-перше, візьміть свій клас C ++ і створіть функції "обгортки" для взаємодії з ним. Наприклад, якщо ми маємо цей клас 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);
};

Потім ми реалізуємо такі функції 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;
}

Потім заголовок моста містить:

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

Від Swift тепер ми можемо створити екземпляр об’єкта та взаємодіяти з ним так:

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!

Отже, як бачите, рішення (яке насправді досить просте) полягає у створенні обгортки, які створюватимуть екземпляр об’єкта та повертатимуть покажчик на цей об’єкт. Потім це може бути передано назад у функції обгортки, які можуть легко розглядати його як об'єкт, що відповідає цьому класу, і викликати функції-члени.

Робимо його чистішим

Хоча це фантастичний початок і доводить, що використовувати існуючі класи C ++ із тривіальним мостом цілком реально, він може бути навіть чистішим.

Очищення цього просто означало б, що ми видаляємо UnsafeMutablePointer<Void>з середини нашого коду Swift і інкапсулюємо його в клас Swift. По суті, ми використовуємо ті самі функції обгортки C / C ++, але взаємодіємо їх із класом Swift. Клас Swift підтримує посилання на об'єкт і, по суті, просто передає всі посилання на метод та атрибут через міст до об'єкта C ++!

Зробивши це, весь мостовий код повністю інкапсульований у клас Swift. Незважаючи на те, що ми все ще використовуємо міст C, ми ефективно використовуємо об'єкти C ++ прозоро, не маючи необхідності перекодувати їх у Objective-C або Objective-C ++.


12
Тут є кілька важливих пропущених питань. Перший - це те, що деструктор ніколи не викликається, а об’єкт витікає. Друге - винятки можуть спричинити неприємні проблеми.
Майкл Андерсон,

2
Я висвітлив купу питань у своїй відповіді на це питання stackoverflow.com/questions/2045774/…
Майкл Андерсон

2
@DavidHoelzer, я просто хотів наголосити на цій ідеї, тому що деякі розробники спробують обернути цілу бібліотеку C ++ і використовувати її так, як це було написано в Swift. Ви дали чудовий приклад того, як "створювати екземпляри та маніпулювати класом C ++ із Swift"! І я просто хотів додати, що цю техніку слід застосовувати обережно! Дякую за приклад!
Ніколай Ніта

1
Не думайте, що це «ідеально». Я думаю, це майже те саме, що ви пишете обгортку Objective-C, а потім використовуєте її в Swift.
Bagusflyer

1
@Bagusflyer впевнений ... за винятком того, що це зовсім не обгортка об'єктива-С.
Девід Хольцер

11

Swift наразі не має взаємодії C ++. Це довготермінова мета, але навряд чи це станеться найближчим часом.


25
Називання "використовувати обгортки C" формою "взаємодії C ++" досить сильно розтягує цей термін
Catfish_Man

6
Можливо, але використання їх, щоб дозволити вам створити екземпляр класу C ++, а потім викликати методи, робить це корисним і задовольняє питання.
Девід Хольцер

2
Насправді це вже не довгострокова мета, а натомість вона позначена як "поза обсягом" у дорожній карті.
Девід Хольцер

4
використання c-обгортки є стандартним підходом для майже всіх взаємодій C ++ з будь-якою мовою, python, java тощо
Ендрю Пол Сіммонс

8

На додаток до власного рішення, існує ще один спосіб це зробити. Ви можете зателефонувати або написати безпосередньо код C ++ на об’єкті-c ++.

Таким чином, ви можете створити обгортку-C ++ поверх коду на C ++ і створити відповідний інтерфейс.

Потім зателефонуйте коду-C ++ зі свого швидкого коду. Щоб мати можливість писати код-C ++, можливо, доведеться перейменувати розширення файлу з .m на .mm

Не забудьте звільнити пам’ять, виділену об’єктами С ++, коли це підходить.


6

Як зазначено в іншій відповіді, використовувати ObjC ++ для взаємодії набагато простіше. Просто назвіть свої файли .mm замість .m та xcode / clang, надаючи вам доступ до c ++ у цьому файлі.

Зверніть увагу, що ObjC ++ не підтримує успадкування C ++. Якщо ви хочете підкласувати клас c ++ в ObjC ++, ви не можете. Вам доведеться написати підклас на C ++ і обернути його навколо класу ObjC ++.

Потім використовуйте мостовий заголовок, який ви зазвичай використовуєте для виклику objc від swift.


2
Можливо, ви пропустили суть. Інтерфейси потрібні, оскільки ми маємо дуже велику мультиплатформенну програму C ++, яку зараз підтримуємо і в OS X. Завдання C ++ насправді не є варіантом для всього проекту.
Девід Хольцер,

Я не впевнений, що розумію вас. Вам просто потрібен ObjC ++, коли ви хочете, щоб дзвінок переходив між швидким та c ++ або навпаки. Просто межі потрібно писати в objc ++, а не весь проект.
nnrales,

1
Так, я переглянув ваше запитання. Ви, схоже, хочете безпосередньо маніпулювати C ++ з швидкої швидкості. Я не знаю, як це зробити.
nnrales

1
Ось чого досягла моя розміщена відповідь. Функції C дозволяють передавати об'єкти всередину і виводити їх як довільний об'єм пам'яті та викликати методи з будь-якої сторони.
Девід Хольцер,

1
@DavidHoelzer Вважаєш, що це круто, але хіба ти таким чином не ускладнюєш код? Я думаю, це суб’єктивно. Обгортка ObjC ++ мені здається чистішою.
nnrales

5

Ви можете використовувати Scapix Language Bridge для автоматичного мостування C ++ до Swift (серед інших мов). Мостовий код автоматично генерується на льоту безпосередньо із заголовних файлів C ++. Ось приклад :

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

Стрімкий:

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

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

Цікаво. Схоже, ви вперше це випустили в березні 2019 року.
Девід Хольцер,

@ boris-rasin Я не можу знайти прямий c ++ до швидкого мосту у прикладі. Чи є ця функція в планах?
sage444

2
Так, прямого мосту С ++ на Свіфт на даний момент немає. Але мост від C ++ до ObjC чудово працює для Swift (через міст ObjC до Swift від Apple). Зараз я працюю над прямим мостом (в деяких випадках це забезпечить кращу продуктивність).
Борис Расін

1
О так, ви абсолютно праві, але в голому стрімкому проекті на Linux це не так. З нетерпінням чекаємо на вашу реалізацію.
sage444
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.