Berinteraksi dengan kelas C ++ dari Swift

89

Saya memiliki perpustakaan kelas yang signifikan yang ditulis dalam C ++. Saya mencoba memanfaatkannya melalui beberapa jenis penghubung dalam Swift daripada menulis ulang sebagai kode Swift. Motivasi utamanya adalah bahwa kode C ++ mewakili pustaka inti yang digunakan pada banyak platform. Secara efektif, saya hanya membuat UI berbasis Swift untuk memungkinkan fungsionalitas inti berfungsi di bawah OS X.

Ada pertanyaan lain yang menanyakan, "Bagaimana cara memanggil fungsi C ++ dari Swift." Ini bukan pertanyaan saya. Untuk menjembatani ke fungsi C ++, berikut ini berfungsi dengan baik:

Tentukan header penghubung melalui "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 */

Kode Swift sekarang dapat memanggil fungsi secara langsung

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

Pertanyaan saya lebih spesifik. Bagaimana cara saya membuat instance dan memanipulasi Kelas C ++ dari dalam Swift? Saya tidak dapat menemukan apa pun yang dipublikasikan tentang ini.

David Hoelzer
sumber
9
Saya pribadi terpaksa menulis file pembungkus Objective-C ++ biasa yang mengekspos kelas Objective-C yang mereproduksi semua panggilan C ++ yang relevan dan hanya meneruskannya ke instance yang dipegang dari kelas C ++. Dalam kasus saya jumlah kelas dan panggilan C ++ kecil jadi tidak terlalu padat karya. Tetapi saya akan menunda untuk menganjurkan ini sebagai jawaban dengan harapan seseorang akan menemukan sesuatu yang lebih baik.
Tommy
1
Yah, itu sesuatu ... Mari kita tunggu dan lihat (dan berharap).
David Hoelzer
Saya telah menerima saran melalui IRC untuk menulis kelas pembungkus Swift yang mempertahankan penunjuk kosong ke objek C ++ sebenarnya dan memperlihatkan metode yang diperlukan yang, secara efektif, baru saja melewati jembatan C dan penunjuk ke objek.
David Hoelzer
4
Sebagai pembaruan untuk ini, Swift 3.0 sekarang keluar dan, terlepas dari janji sebelumnya, interoperabilitas C ++ sekarang ditandai sebagai "Di luar cakupan".
David Hoelzer

Jawaban:

63

Saya telah menyusun jawaban yang bisa dikelola dengan sempurna. Seberapa bersih Anda menginginkan hal ini sepenuhnya didasarkan pada seberapa banyak pekerjaan yang ingin Anda lakukan.

Pertama, ambil kelas C ++ Anda dan buat fungsi "pembungkus" C untuk berinteraksi dengannya. Misalnya, jika kita memiliki kelas C ++ ini:

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

Kami kemudian mengimplementasikan fungsi C ++ ini:

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

Header jembatan kemudian berisi:

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

Dari Swift, kita sekarang dapat membuat instance objek dan berinteraksi dengannya seperti:

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!

Jadi, seperti yang Anda lihat, solusinya (yang sebenarnya cukup sederhana) adalah membuat pembungkus yang akan membuat instance objek dan mengembalikan pointer ke objek itu. Ini kemudian dapat dikirimkan kembali ke fungsi pembungkus yang dapat dengan mudah memperlakukannya sebagai objek yang sesuai dengan kelas itu dan memanggil fungsi anggota.

Menjadikannya Lebih Bersih

Meskipun ini adalah awal yang fantastis dan membuktikan bahwa sangat layak untuk menggunakan kelas C ++ yang ada dengan jembatan yang sepele, ini bahkan bisa lebih bersih.

Membersihkan ini berarti kita menghapus UnsafeMutablePointer<Void>bagian tengah kode Swift kita dan merangkumnya ke dalam kelas Swift. Pada dasarnya, kami menggunakan fungsi pembungkus C / C ++ yang sama tetapi menghubungkannya dengan kelas Swift. Kelas Swift mempertahankan referensi objek dan pada dasarnya hanya meneruskan semua panggilan referensi metode dan atribut melalui jembatan ke objek C ++!

Setelah melakukan ini, semua kode penghubung benar-benar dikemas dalam kelas Swift. Meskipun kami masih menggunakan C bridge, kami secara efektif menggunakan objek C ++ secara transparan tanpa harus menggunakan pengodean ulang di Objective-C atau Objective-C ++.

David Hoelzer
sumber
13
Ada beberapa masalah penting yang terlewat di sini. Yang pertama adalah bahwa destruktor tidak pernah dipanggil, dan objeknya bocor. Yang kedua adalah bahwa pengecualian dapat menyebabkan masalah besar.
Michael Anderson
2
Saya membahas banyak masalah dalam jawaban saya untuk pertanyaan ini stackoverflow.com/questions/2045774/…
Michael Anderson
2
@DavidHoelzer, saya hanya ingin menekankan gagasan ini karena beberapa pengembang akan mencoba membungkus seluruh pustaka C ++ dan menggunakannya seperti yang tertulis di Swift. Anda memberikan contoh yang bagus tentang cara "membuat instance dan memanipulasi Kelas C ++ dari dalam Swift"! Dan saya hanya ingin menambahkan bahwa teknik ini harus digunakan dengan hati-hati! Terima kasih atas teladan Anda!
Nicolai Nita
1
Jangan berpikir ini "sempurna". Saya pikir ini hampir sama seperti Anda menulis pembungkus Objective-C lalu menggunakannya di Swift.
Bagusflyer
1
@Bagusflyer yakin ... kecuali bahwa ini bukan pembungkus objektif-C sama sekali.
David Hoelzer
11

Swift tidak memiliki interop C ++ saat ini. Ini adalah tujuan jangka panjang, tetapi sangat tidak mungkin terjadi dalam waktu dekat.

Catfish_Man
sumber
25
Menyebut "gunakan pembungkus C" sebagai bentuk dari "C ++ interop" sedikit memperluas istilah
Catfish_Man
6
Mungkin, tetapi menggunakannya untuk memungkinkan Anda membuat instance kelas C ++ dan kemudian memanggil metode di atasnya membuatnya berguna dan memenuhi pertanyaan.
David Hoelzer
2
Faktanya, ini bukan lagi tujuan jangka panjang, tetapi ditandai sebagai "Di luar cakupan" di peta jalan.
David Hoelzer
4
menggunakan c-wrappers adalah pendekatan standar untuk hampir semua C ++ interop ke bahasa apa pun, python, java, dll.
Andrew Paul Simmons
8

Selain solusi Anda sendiri, ada cara lain untuk melakukannya. Anda dapat memanggil atau langsung menulis kode C ++ di objektif-c ++.

Jadi Anda dapat membuat pembungkus objektif-C ++ di atas kode C ++ Anda dan membuat antarmuka yang sesuai.

Kemudian panggil kode tujuan-C ++ dari kode swift Anda. Untuk dapat menulis kode objektif-C ++ Anda mungkin harus mengganti nama ekstensi file dari .m menjadi .mm

Jangan lupa untuk melepaskan memori yang dialokasikan oleh objek C ++ Anda jika cocok.

Ahmed
sumber
7

Anda dapat menggunakan Scapix Language Bridge untuk secara otomatis menjembatani C ++ ke Swift (di antara bahasa lain). Kode jembatan secara otomatis dibuat dengan cepat langsung dari file header C ++. Berikut ini contohnya :

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

Cepat:

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

        contact.sendMessage("Hello", friend)
        contact.addTags(["a","b","c"])
        contact.addFriends([friend])
    }
}
Boris Rasin
sumber
Menarik. Sepertinya Anda baru saja merilis ini untuk pertama kalinya pada Maret 2019.
David Hoelzer
@ boris-rasin Saya tidak dapat menemukan c ++ langsung ke jembatan cepat dalam contoh. Apakah Anda memiliki fitur ini dalam paket?
sage444
2
Ya, saat ini tidak ada C ++ langsung ke jembatan Swift. Tetapi jembatan C ++ ke ObjC berfungsi untuk Swift dengan sangat baik (melalui jembatan ObjC ke Swift Apple). Saya sedang mengerjakan jembatan langsung sekarang (ini akan memberikan kinerja yang lebih baik dalam beberapa kasus).
Boris Rasin
1
Oh ya, Anda benar sekali, tetapi dalam proyek yang sangat cepat di linux ini tidak terjadi. Menantikan implementasi Anda.
sage444
6

Seperti jawaban lain yang disebutkan, menggunakan ObjC ++ untuk berinteraksi jauh lebih mudah. Cukup beri nama file Anda .mm, bukan .m dan xcode / clang, memberi Anda akses ke c ++ di file itu.

Perhatikan bahwa ObjC ++ tidak mendukung pewarisan C ++. Saya Anda ingin membuat subclass kelas c ++ di ObjC ++, Anda tidak bisa. Anda harus menulis subclass dalam C ++ dan membungkusnya di sekitar kelas ObjC ++.

Kemudian gunakan header penghubung yang biasanya Anda gunakan untuk memanggil objc dari swift.

nnrales
sumber
2
Anda mungkin telah melewatkan intinya. Antarmuka diperlukan karena kami memiliki aplikasi C ++ multi-platform yang sangat besar yang sekarang kami dukung di OS X juga. Objective C ++ sebenarnya bukanlah pilihan untuk keseluruhan proyek.
David Hoelzer
Saya tidak yakin saya mengerti Anda. Anda hanya perlu ObjC ++ saat Anda ingin panggilan beralih antara swift dan c ++ atau sebaliknya. Hanya batasan yang perlu ditulis dalam objc ++, bukan keseluruhan proyek.
nnrales
1
Ya, saya membahas pertanyaan Anda. Anda tampaknya ingin memanipulasi C ++ secara langsung dari swift. Saya tidak tahu bagaimana melakukan itu.
nnrales
1
Itulah yang dicapai jawaban saya yang diposting. Fungsi C memungkinkan Anda untuk melewatkan objek masuk dan keluar sebagai kumpulan sembarang memori dan untuk memanggil metode di kedua sisi.
David Hoelzer
1
@DavidHoelzer Sepertinya ini keren, tetapi bukankah Anda membuat kode lebih rumit dengan cara ini? Saya kira itu subjektif. Pembungkus ObjC ++, tampak lebih bersih bagi saya.
nnrales