与Swift中的C ++类进行交互


86

我有一个大量的用C ++编写的类库。我正在尝试通过Swift中的某种桥接来使用它们,而不是将它们重写为Swift代码。主要动机是C ++代码表示一个在多个平台上使用的核心库。实际上,我只是在创建一个基于Swift的UI,以允许核心功能在OS X下工作。

还有其他问题,“如何从Swift调用C ++函数”。这不是我的问题。要桥接到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))

我的问题更具体。如何在Swift中实例化和操作C ++类?我似乎找不到任何发布的内容。


8
我亲自采取了编写普通的Objective-C ++包装器文件的方式,这些文件公开了一个Objective-C类,该类再现了所有相关的C ++调用,并将它们简单地转发到C ++类的一个保留实例。就我而言,C ++类和调用的数量很少,因此不是特别费力。但是,我将不提倡将此作为答案,希望有人会提出更好的建议。
汤米

1
好吧,这是……让我们拭目以待(和希望)。
David Hoelzer

我已经通过IRC提出了一个建议,即编写一个Swift包装器类,该类维护指向实际C ++对象的void指针,并公开所需的方法,这些方法实际上是通过C桥和指向该对象的指针有效传递的。
David Hoelzer

4
作为对此的更新,Swift 3.0现在已退出市场,尽管有先前的承诺,但C ++互操作性现在被标记为“超出范围”。
David Hoelzer

Answers:


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中编写的那样使用它。您举了一个很好的例子,说明如何“在Swift中实例化和操纵C ++类”!我只是想补充一点,应谨慎使用此技术!谢谢您的榜样!
Nicolai Nita

1
不要以为这是“完美的”。我认为这几乎与您编写一个Objective-C包装器然后在Swift中使用它相同。
Bagusflyer

1
@Bagusflyer当然...除了根本不是目标C包装程序。
David Hoelzer

11

Swift目前没有C ++互操作。这是一个长期目标,但在不久的将来不太可能发生。


25
将“使用C包装程序”称为“ C ++互操作”的形式在很大程度上扩展了该术语
Catfish_Man 2016年

6
也许可以,但是使用它们允许您实例化C ++类,然后在其上调用方法使其变得有用并满足该问题。
David Hoelzer

2
实际上,它不再是一个长期目标,而是在路线图中被标记为“超出范围”。
David Hoelzer

4
使用C-包装是几乎所有C ++的互操作,以任何语言,Python和Java等标准方法
安德鲁·保罗·西蒙斯

8

除了您自己的解决方案外,还有另一种方法可以做到这一点。您可以在Objective-C ++中调用或直接编写C ++代码。

因此,您可以在C ++代码之上创建一个Objective-C ++包装器,并创建一个合适的接口。

然后从您的快捷代码中调用Objective-C ++代码。为了能够编写Objective-C ++代码,您可能必须将文件扩展名从.m重命名为.mm

如果合适,不要忘记释放由C ++对象分配的内存。


6

正如提到的另一个答案,使用ObjC ++进行交互要容易得多。只需将文件命名为.mm而不是.m和xcode / clang,即可访问该文件中的c ++。

请注意,ObjC ++不支持C ++继承。我想在ObjC ++中子类化c ++类,但是不能。您将不得不用C ++编写子类,并将其包装在ObjC ++类中。

然后使用通常用于从swift调用objc的桥接头。


2
您可能已经错过了重点。需要接口是因为我们有一个非常大的,多平台的C ++应用程序,现在我们也支持OSX。实际上,Objective C ++并不是整个项目的选择。
David Hoelzer '18

我不确定我是否理解你。如果您希望在swift和c ++之间进行调用,或者反之亦然,则只需要ObjC ++。只是边界需要用objc ++编写,而不是整个项目。
nnrales

1
是的,我已经解决了您的问题。您似乎正在寻求直接快速地操作C ++。我不知道该怎么做。
nnrales

1
这就是我发布的答案所完成的。C函数允许您将对象作为任意的内存块传入和传出,并在任一端调用方法。
David Hoelzer

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
David Hoelzer

@ boris-rasin在示例中我找不到直接的C ++到Swift Bridge。计划中是否有此功能?
sage444

2
是的,目前没有直接C ++到Swift的桥梁。但是从C ++到ObjC的桥接器可以完美地完成Swift的工作(通过Apple的ObjC到Swift的桥接器)。我现在正在直接桥上工作(在某些情况下它将提供更好的性能)。
鲍里斯·拉辛

1
哦,是的,您是完全正确的,但是在Linux上的裸项目中却并非如此。期待着您的实施。
sage444 '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.