Swift'in C ++ sınıflarıyla etkileşim


89

C ++ ile yazılmış önemli bir sınıf kitaplığım var. Onları Swift kodu olarak yeniden yazmak yerine, Swift içindeki bir tür köprü aracılığıyla kullanmaya çalışıyorum. Birincil motivasyon, C ++ kodunun birden çok platformda kullanılan bir çekirdek kitaplığı temsil etmesidir. Etkili bir şekilde, temel işlevin OS X altında çalışmasına izin vermek için Swift tabanlı bir kullanıcı arayüzü oluşturuyorum.

"Swift'in C ++ işlevini nasıl çağırırım" diye soran başka sorular da var. Benim sorum bu değil . Bir C ++ işlevine köprü oluşturmak için aşağıdakiler iyi çalışır:

"C" üzerinden bir köprüleme başlığı tanımlayın

#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 kodu artık işlevleri doğrudan çağırabilir

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

Sorum daha spesifik. Swift'in içinden bir C ++ Sınıfını nasıl başlatabilir ve işleyebilirim ? Bu konuda yayınlanan bir şey bulamıyorum.


9
Kişisel olarak, tüm ilgili C ++ çağrılarını yeniden üreten ve bunları C ++ sınıfının tutulan bir örneğine yönlendiren bir Objective-C sınıfını ortaya çıkaran düz Objective-C ++ sarmalayıcı dosyaları yazmaya başvurdum. Benim durumumda C ++ sınıflarının ve çağrılarının sayısı azdır, bu nedenle özellikle emek yoğun değildir. Ancak, birisinin daha iyi bir şey bulacağı umuduyla bunu bir cevap olarak savunmaya devam edeceğim.
Tommy

1
Şey, bu bir şey ... Bekleyelim ve görelim (ve umut edelim).
David Hoelzer

Gerçek C ++ nesnesine bir boşluk gösterici tutan ve C köprüsünden ve göstericiden nesneye etkili bir şekilde geçen gerekli yöntemleri ortaya çıkaran bir Swift sarıcı sınıfı yazmak için IRC aracılığıyla bir öneri aldım.
David Hoelzer

4
Bunun bir güncellemesi olarak, Swift 3.0 artık çıktı ve önceki sözlere rağmen, C ++ birlikte çalışabilirliği artık "Kapsam dışı" olarak işaretlendi.
David Hoelzer

Yanıtlar:


63

Mükemmel şekilde yönetilebilir bir cevap buldum. Bunun ne kadar temiz olmasını istediğiniz, tamamen ne kadar iş yapmak istediğinize bağlıdır.

İlk olarak, C ++ sınıfınızı alın ve onunla arayüz oluşturmak için C "sarmalayıcı" işlevleri oluşturun. Örneğin, bu C ++ sınıfına sahipsek:

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

Daha sonra şu C ++ işlevlerini uygularız:

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

Köprü başlığı daha sonra şunları içerir:

#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'den artık nesneyi somutlaştırabilir ve onunla şu şekilde etkileşim kurabiliriz:

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!

Gördüğünüz gibi, çözüm (aslında oldukça basit) bir nesneyi başlatacak ve o nesneye bir işaretçi döndürecek sarmalayıcılar yaratmaktır. Bu daha sonra, onu bu sınıfa uyan bir nesne olarak kolayca değerlendirebilen ve üye işlevlerini çağırabilen sarmalayıcı işlevlerine geri aktarılabilir.

Temizlemek

Bu harika bir başlangıç ​​ve mevcut C ++ sınıflarını önemsiz bir köprü ile kullanmanın tamamen mümkün olduğunu kanıtlasa da, daha da temiz olabilir.

Bunu temizlemek, UnsafeMutablePointer<Void>Swift kodumuzun ortasından kaldırıp Swift sınıfına dahil etmemiz anlamına gelir . Esasen, aynı C / C ++ sarmalayıcı işlevlerini kullanıyoruz, ancak bunları bir Swift sınıfıyla arabirimlendiriyoruz. Swift sınıfı, nesne başvurusunu korur ve esasen tüm yöntem ve öznitelik başvuru çağrılarını köprü üzerinden C ++ nesnesine geçirir!

Bunu yaptıktan sonra, tüm köprüleme kodu tamamen Swift sınıfında özetlenmiştir. Hala bir C köprüsü kullanıyor olsak da, C ++ nesnelerini Objective-C veya Objective-C ++ 'da yeniden kodlamak zorunda kalmadan şeffaf bir şekilde kullanıyoruz.


13
Burada gözden kaçan birkaç önemli konu var. İlki, yıkıcı asla çağrılmaz ve nesne sızdırılır. İkincisi, istisnaların kötü sorunlara neden olabileceğidir.
Michael Anderson

2
Bu soruya verdiğim yanıtta bir dizi konuyu ele aldım stackoverflow.com/questions/2045774/…
Michael Anderson

2
@DavidHoelzer, bu fikri vurgulamak istedim çünkü bazı geliştiriciler bütün bir C ++ kitaplığını sarmaya ve Swift'de yazıldığı gibi kullanmaya çalışacaklar. "Swift içinden bir C ++ Sınıfının nasıl başlatılacağı ve işleneceği" konusunda harika bir örnek verdiniz! Ve ben sadece bu tekniğin dikkatli kullanılması gerektiğini eklemek istedim! Örneğiniz için teşekkür ederiz!
Nicolai Nita

1
Bunun "mükemmel" olduğunu düşünmeyin. Bence bir Objective-C sarıcı yazıp Swift'de kullanmakla neredeyse aynı.
Bagusflyer

1
@Bagusflyer elbette ... bunun bir hedef-C sarmalayıcı olmaması dışında
David Hoelzer

11

Swift şu anda C ++ birlikte çalışmasına sahip değil. Bu uzun vadeli bir hedef, ancak yakın gelecekte gerçekleşmesi pek olası değil.


25
"C sarmalayıcıları kullan" olarak adlandırmak, bir "C ++ birlikte çalışma" biçimi terimi oldukça
esnetiyor

6
Belki, ancak bir C ++ sınıfını başlatmanıza ve ardından bunun üzerine yöntemler çağırmanıza izin vermek için bunları kullanmak, onu yararlı hale getirir ve soruyu tatmin eder.
David Hoelzer

2
Aslında, artık uzun vadeli bir hedef değil, bunun yerine yol haritasında "Kapsam dışı" olarak işaretleniyor.
David Hoelzer

4
c-sarmalayıcıların kullanılması, neredeyse tüm C ++ ile herhangi bir dil, python, java vb. ile birlikte çalışmak için standart yaklaşımdır
Andrew Paul Simmons

8

Kendi çözümünüze ek olarak, bunu yapmanın başka bir yolu var. Object-c ++ ile C ++ kodunu çağırabilir veya doğrudan yazabilirsiniz.

Böylelikle C ++ kodunuzun üzerine bir hedef-C ++ sarıcı oluşturabilir ve uygun bir arayüz oluşturabilirsiniz.

Ardından, swift kodunuzdan objektif-C ++ kodunu çağırın. Objektif-C ++ kodu yazabilmek için dosya uzantısını .m'den .mm'ye yeniden adlandırmanız gerekebilir.

Uygun olduğunda C ++ nesneleriniz tarafından ayrılan belleği serbest bırakmayı unutmayın.


7

Scapix Language Bridge'i , C ++ ile Swift arasında otomatik olarak köprü oluşturmak için kullanabilirsiniz (diğer dillerin yanı sıra). Köprü kodu, doğrudan C ++ başlık dosyalarından anında otomatik olarak oluşturulur. İşte bir örnek :

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

İlginç. Görünüşe göre bunu ilk kez Mart 2019'da
yayınlamışsınız.

@ boris-rasin Örnekte hızlı köprüye doğrudan c ++ bulamıyorum. Planlarda bu özellik var mı?
sage444

2
Evet, şu anda Swift köprüsüne doğrudan C ++ yoktur. Ancak C ++ - ObjC köprüsü, Swift için mükemmel şekilde çalışıyor (Apple'ın ObjC'sinden Swift köprüsüne). Şu anda doğrudan köprü üzerinde çalışıyorum (bazı durumlarda daha iyi performans sağlayacaktır).
Boris Rasin

1
Oh evet, tamamen haklısınız, ancak linux üzerindeki çıplak hızlı projede durum böyle değil. Uygulamanıza bakmayı dört gözle bekliyorum.
sage444

6

Başka bir cevapta belirtildiği gibi, etkileşim için ObjC ++ kullanmak çok daha kolaydır. Dosyalarınızı .m ve xcode / clang yerine .mm olarak adlandırın, o dosyada c ++ 'a erişmenizi sağlar.

ObjC ++ 'nın C ++ kalıtımını desteklemediğini unutmayın. ObjC ++ 'da bir c ++ sınıfının alt sınıfını oluşturmak istiyorsanız, yapamazsınız. Alt sınıfı C ++ 'da yazmanız ve bir ObjC ++ sınıfının etrafına sarmanız gerekir.

Daha sonra objc'yi swift'ten çağırmak için normalde kullanacağınız köprüleme başlığını kullanın.


2
Önemli noktayı kaçırmış olabilirsiniz. Arayüzler gereklidir çünkü artık OS X'te de desteklediğimiz çok büyük, çok platformlu bir C ++ uygulamamız var. Amaç C ++ gerçekten tüm proje için bir seçenek değildir.
David Hoelzer

Seni anladığımdan emin değilim. Bir çağrının swift ve c ++ arasında veya tam tersi arasında gitmesini istediğinizde sadece ObjC ++ 'ya ihtiyacınız var. Tüm proje değil, yalnızca sınırların objc ++ ile yazılması gerekir.
nnrales

1
Evet, sorunuzu gözden geçirdim. C ++ 'ı hızlı bir şekilde doğrudan değiştirmek istiyor gibisin. Bunu nasıl yapacağımı bilmiyorum.
nnrales

1
Gönderdiğim cevabım bunu başarıyor. C işlevleri, nesneleri keyfi bellek parçaları olarak içeri ve dışarı aktarmanıza ve her iki tarafta da yöntemleri çağırmanıza izin verir.
David Hoelzer

1
@DavidHoelzer Harika olduğunu düşünüyor, ancak kodu bu şekilde daha karmaşık hale getirmiyor musunuz? Sanırım öznel. ObjC ++ sarmalayıcı bana daha temiz görünüyor.
nnrales
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.