การโต้ตอบกับคลาส C ++ จาก Swift


89

ฉันมีไลบรารีชั้นเรียนที่สำคัญที่เขียนด้วย C ++ ฉันพยายามใช้ประโยชน์จากบริดจ์บางประเภทภายใน Swift แทนที่จะเขียนใหม่เป็น Swift code แรงจูงใจหลักคือรหัส C ++ แสดงถึงไลบรารีหลักที่ใช้กับหลายแพลตฟอร์ม อย่างมีประสิทธิภาพฉันแค่สร้าง UI ที่ใช้ 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 ได้อย่างไร ฉันไม่พบสิ่งที่เผยแพร่ในเรื่องนี้


9
ฉันได้ใช้การเขียนไฟล์ Wrapper Objective-C ++ ธรรมดาเป็นการส่วนตัวที่แสดงคลาส Objective-C ซึ่งสร้างการเรียก C ++ ที่เกี่ยวข้องทั้งหมดและส่งต่อไปยังอินสแตนซ์ของคลาส C ++ ในกรณีของฉันจำนวนคลาสและการโทร C ++ มีน้อยดังนั้นจึงไม่ได้ใช้แรงงานมากเป็นพิเศษ แต่ฉันจะไม่สนับสนุนสิ่งนี้เพื่อเป็นคำตอบด้วยความหวังว่าจะมีใครบางคนคิดสิ่งที่ดีกว่า
Tommy

1
มันคือบางสิ่งบางอย่าง ... รอดู (และหวัง)
David Hoelzer

ฉันได้รับคำแนะนำผ่าน IRC ให้เขียนคลาส Swift wrapper ที่รักษาตัวชี้โมฆะไปยังวัตถุ C ++ จริงและแสดงวิธีการที่จำเป็นซึ่งมีประสิทธิภาพเพียงแค่ส่งผ่านสะพาน C และตัวชี้ไปยังวัตถุ
David Hoelzer

4
ในการอัปเดตนี้ Swift 3.0 ได้ออกให้บริการแล้วและแม้ว่าจะมีสัญญาก่อนหน้านี้ความสามารถในการทำงานร่วมกันของ C ++ ก็ถูกทำเครื่องหมายว่า "อยู่นอกขอบเขต"
David Hoelzer

คำตอบ:


63

ฉันได้คำตอบที่จัดการได้อย่างสมบูรณ์แบบแล้ว ความสะอาดที่คุณต้องการให้เป็นขึ้นอยู่กับจำนวนงานที่คุณเต็มใจทำ

ขั้นแรกให้ใช้คลาส C ++ ของคุณและสร้างฟังก์ชัน C "wrapper" เพื่อเชื่อมต่อกับมัน ตัวอย่างเช่นถ้าเรามีคลาส 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!

ดังที่คุณเห็นวิธีแก้ปัญหา (ซึ่งจริงๆแล้วค่อนข้างง่าย) คือการสร้าง Wrapper ที่จะสร้างอินสแตนซ์ของวัตถุและส่งตัวชี้กลับไปยังวัตถุนั้น จากนั้นสามารถส่งกลับไปยังฟังก์ชัน wrapper ซึ่งสามารถถือว่าเป็นอ็อบเจ็กต์ที่สอดคล้องกับคลาสนั้นและเรียกใช้ฟังก์ชันสมาชิก

ทำให้สะอาดขึ้น

แม้ว่านี่จะเป็นการเริ่มต้นที่ยอดเยี่ยมและพิสูจน์ให้เห็นว่าเป็นไปได้อย่างสมบูรณ์ที่จะใช้คลาส C ++ ที่มีอยู่กับบริดจ์เล็กน้อย แต่ก็สามารถทำได้ดีกว่า

การทำความสะอาดสิ่งนี้หมายความว่าเราลบออกUnsafeMutablePointer<Void>จากตรงกลางของรหัส Swift ของเราและห่อหุ้มไว้ในคลาส Swift โดยพื้นฐานแล้วเราใช้ฟังก์ชัน Wrapper C / C ++ เดียวกัน แต่เชื่อมต่อกับคลาส Swift คลาส Swift จะรักษาการอ้างอิงอ็อบเจ็กต์และโดยพื้นฐานแล้วเพียงแค่ส่งการเรียกใช้เมธอดและแอ็ตทริบิวต์ทั้งหมดผ่านบริดจ์ไปยังอ็อบเจ็กต์ C ++!

เมื่อทำสิ่งนี้แล้วรหัสบริดจ์ทั้งหมดจะถูกห่อหุ้มไว้ในคลาส Swift อย่างสมบูรณ์ แม้ว่าเราจะยังใช้สะพาน C อยู่ แต่เราก็ใช้วัตถุ C ++ ได้อย่างมีประสิทธิภาพอย่างโปร่งใสโดยไม่ต้องใช้การเข้ารหัสใหม่ใน Objective-C หรือ Objective-C ++


13
มีปัญหาสำคัญสองประการที่พลาดไปที่นี่ สิ่งมีชีวิตแรกที่ไม่เคยถูกเรียกว่าผู้ทำลายและวัตถุนั้นรั่วไหล ประการที่สองคือข้อยกเว้นอาจทำให้เกิดปัญหาที่น่ารังเกียจ
Michael Anderson

2
ฉันพูดถึงประเด็นต่างๆมากมายในคำตอบสำหรับคำถามนี้stackoverflow.com/questions/2045774/…
Michael Anderson

2
@DavidHoelzer ฉันแค่อยากจะเน้นความคิดนี้เพราะนักพัฒนาบางคนจะพยายามรวมไลบรารี C ++ ทั้งหมดและใช้มันตามที่เขียนใน Swift คุณได้ยกตัวอย่างวิธีการ "สร้างอินสแตนซ์และปรับแต่งคลาส C ++ จากภายใน Swift"! และฉันแค่อยากจะเพิ่มว่าควรใช้เทคนิคนี้ด้วยความระมัดระวัง! ขอบคุณสำหรับตัวอย่าง!
Nicolai Nita

1
อย่าคิดว่านี่คือ "สมบูรณ์แบบ" ฉันคิดว่ามันเกือบจะเหมือนกับที่คุณเขียน Objective-C wrapper จากนั้นใช้ใน Swift
Bagusflyer

1
@Bagusflyer แน่นอน ... ยกเว้นว่ามันไม่ใช่เสื้อคลุมวัตถุประสงค์ -C เลย
David Hoelzer

11

Swift ไม่มีการทำงานร่วมกันของ C ++ ในขณะนี้ เป็นเป้าหมายระยะยาว แต่ไม่น่าจะเกิดขึ้นในอนาคตอันใกล้นี้


25
การเรียก "ใช้ C wrappers" รูปแบบของ "C ++ interop" เป็นการยืดระยะค่อนข้างน้อย
Catfish_Man

6
บางที แต่การใช้เพื่อให้คุณสร้างอินสแตนซ์คลาส C ++ จากนั้นเรียกใช้เมธอดเมื่อมันมีประโยชน์และตอบสนองคำถาม
David Hoelzer

2
ในความเป็นจริงมันไม่ใช่เป้าหมายระยะยาวอีกต่อไป แต่กลับถูกทำเครื่องหมายว่า "อยู่นอกขอบเขต" ในแผนงาน
David Hoelzer

4
การใช้ c-wrappers เป็นแนวทางมาตรฐานสำหรับการทำงานร่วมกันของ C ++ เกือบทั้งหมดในภาษาใด ๆ python java และอื่น ๆ
Andrew Paul Simmons

8

นอกจากวิธีแก้ปัญหาของคุณเองแล้วยังมีอีกวิธีหนึ่งที่ทำได้ คุณสามารถโทรหรือเขียนโค้ด C ++ โดยตรงใน objective-c ++

ดังนั้นคุณสามารถสร้างเสื้อคลุมวัตถุประสงค์ -C ++ ที่ด้านบนของโค้ด C ++ และสร้างอินเทอร์เฟซที่เหมาะสม

จากนั้นเรียกรหัสวัตถุประสงค์ -C ++ จากรหัสที่รวดเร็วของคุณ เพื่อให้สามารถเขียนโค้ด Object-C ++ ได้คุณอาจต้องเปลี่ยนชื่อไฟล์นามสกุลจาก. m เป็น. mm

อย่าลืมปล่อยหน่วยความจำที่จัดสรรโดยวัตถุ C ++ ของคุณเมื่อเหมาะสม


7

คุณสามารถใช้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 bridge ทำงานกับ Swift ได้ดีอย่างสมบูรณ์แบบ (ผ่าน ObjC ของ Apple ไปยัง Swift bridge) ตอนนี้ฉันกำลังทำงานกับสะพานทางตรง (จะให้ประสิทธิภาพที่ดีขึ้นในบางกรณี)
Boris

1
โอ้ใช่คุณพูดถูกทั้งหมด แต่ในโครงการ bare swift บน linux ไม่เป็นเช่นนั้น รอคอยที่จะดูการใช้งานของคุณ
sage444

6

ดังคำตอบอื่นที่กล่าวถึงการใช้ ObjC ++ เพื่อโต้ตอบนั้นง่ายกว่ามาก เพียงตั้งชื่อไฟล์ของคุณ. mm แทนที่จะเป็น. m และ xcode / clang ให้คุณเข้าถึง c ++ ในไฟล์นั้น

โปรดทราบว่า ObjC ++ ไม่สนับสนุนการสืบทอด C ++ ฉันต้องการซับคลาส c ++ ใน ObjC ++ คุณทำไม่ได้ คุณจะต้องเขียนคลาสย่อยใน C ++ และล้อมรอบคลาส ObjC ++

จากนั้นใช้ bridging header ที่ปกติจะใช้เรียก objc จาก swift


2
คุณอาจพลาดจุด จำเป็นต้องใช้อินเทอร์เฟซเนื่องจากเรามีแอปพลิเคชัน C ++ หลายแพลตฟอร์มที่มีขนาดใหญ่มากซึ่งตอนนี้เราสนับสนุนบน OS X ด้วยเช่นกัน Objective C ++ ไม่ใช่ตัวเลือกสำหรับทั้งโครงการ
David Hoelzer

ฉันไม่แน่ใจว่าฉันเข้าใจคุณ คุณเพียงแค่ต้องการ ObjC ++ เมื่อคุณต้องการโทรระหว่าง swift และ c ++ หรือในทางกลับกัน ขอบเขตต้องเขียนด้วย objc ++ ไม่ใช่ทั้งโครงการ
nnrales

1
ใช่ฉันตอบคำถามของคุณไปแล้ว ดูเหมือนคุณจะต้องการจัดการ C ++ โดยตรงจากการใช้งานที่รวดเร็ว ฉันไม่รู้ว่าต้องทำยังไง
nnrales

1
นั่นคือสิ่งที่ฉันโพสต์คำตอบสำเร็จ ฟังก์ชั่น C ช่วยให้คุณส่งผ่านวัตถุเข้าและออกเป็นหน่วยความจำโดยพลการและเรียกเมธอดจากด้านใดด้านหนึ่ง
David Hoelzer

1
@DavidHoelzer คิดว่ามันเจ๋ง แต่คุณไม่ทำให้รหัสซับซ้อนขึ้นด้วยวิธีนี้หรือ? ฉันเดาว่ามันเป็นเรื่องส่วนตัว กระดาษห่อ ObjC ++ ดูเหมือนจะสะอาดกว่าสำหรับฉัน
nnrales
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.