Взаимодействие с классами C ++ из Swift

87

У меня есть значительная библиотека классов, написанных на C ++. Я пытаюсь использовать их через какой-то тип моста в Swift, а не переписывать их как код Swift. Основная мотивация заключается в том, что код C ++ представляет собой базовую библиотеку, которая используется на нескольких платформах. По сути, я просто создаю пользовательский интерфейс на основе Swift, чтобы основные функции работали под OS X.

Есть и другие вопросы: «Как вызвать функцию C ++ из Swift». Это не мой вопрос. Для перехода к функции C ++ отлично работает следующее:

Определите заголовок моста через "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 ++ теперь помечена как «Out of scope».
David Hoelzer

Ответы:

61

Я придумал вполне управляемый ответ. Насколько чистым вы хотите, чтобы это было, полностью зависит от того, сколько работы вы готовы делать.

Во-первых, возьмите свой класс C ++ и создайте «функции-оболочки» 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 конечно ... за исключением того, что это вообще не обертка от Objective-C.
Дэвид Хельцер,
11

В настоящее время Swift не поддерживает взаимодействие с C ++. Это долгосрочная цель, но вряд ли она произойдет в ближайшем будущем.

Сом - человек
источник
25
Вызов «использовать оболочки C» формой «взаимодействия с C ++» несколько
расширяет
6
Возможно, но использование их для создания экземпляра класса C ++ и последующего вызова методов делает его полезным и отвечает на вопрос.
Дэвид Хельцер
2
Фактически, это больше не долгосрочная цель, а вместо этого в дорожной карте отмечена как «За рамками».
David Hoelzer
4
использование c-wrappers - стандартный подход почти для всех взаимодействий C ++ с любым языком, python, java и т. д.
Эндрю Пол Симмонс,
8

Помимо вашего собственного решения, есть еще один способ сделать это. Вы можете вызвать или напрямую написать код C ++ в objective-c ++.

Таким образом, вы можете создать оболочку объектного C ++ поверх кода C ++ и создать подходящий интерфейс.

Затем вызовите код объектного C ++ из вашего быстрого кода. Чтобы иметь возможность писать объективный код C ++, вам может потребоваться переименовать расширение файла с .m на .mm.

Не забывайте освобождать память, выделенную вашими объектами C ++, когда это необходимо.

Ахмед
источник
6

Как упоминалось в другом ответе, использовать ObjC ++ для взаимодействия намного проще. Просто назовите свои файлы .mm вместо .m и xcode / clang, и вы получите доступ к C ++ в этом файле.

Обратите внимание, что ObjC ++ не поддерживает наследование C ++. Если вы хотите создать подкласс класса C ++ в ObjC ++, вы не можете. Вам нужно будет написать подкласс на C ++ и обернуть его вокруг класса ObjC ++.

Затем используйте заголовок моста, который вы обычно используете для вызова objc из swift.

nnrales
источник
2
Возможно, вы упустили суть. Интерфейсы необходимы, потому что у нас есть очень большое многоплатформенное приложение C ++, которое мы теперь поддерживаем и в OS X. Objective C ++ действительно не подходит для всего проекта.
Дэвид Хельцер
Я не уверен, что понимаю вас. Вам просто нужен ObjC ++, если вы хотите, чтобы вызов выполнялся между Swift и C ++ или наоборот. На objc ++ нужно писать только границы, а не весь проект.
nnrales
1
Да, я ответил на ваш вопрос. Похоже, вы хотите напрямую управлять C ++ из swift. Я не знаю, как это сделать.
nnrales
1
Это то, что выполняет мой опубликованный ответ. Функции C позволяют передавать объекты внутрь и наружу как произвольные блоки памяти и вызывать методы с любой стороны.
Дэвид Хельцер
1
@DavidHoelzer: Думаю, это круто, но разве вы не усложняете код таким образом? Думаю, это субъективно. Обертка ObjC ++ кажется мне чище.
nnrales
6

Вы можете использовать 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);
};

Swift:

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 ++ к Swift. Но мост C ++ в ObjC отлично работает для Swift (через мост Apple ObjC в Swift). Я сейчас работаю над прямым мостом (в некоторых случаях он обеспечивает лучшую производительность).
Борис Расин
1
О да, вы совершенно правы, но в проекте bare swift на Linux это не так. С нетерпением жду вашей реализации.
sage444