Tương tác với các lớp C ++ từ Swift


87

Tôi có một thư viện đáng kể về các lớp được viết bằng C ++. Tôi đang cố gắng sử dụng chúng thông qua một số loại cầu nối trong Swift thay vì viết lại chúng dưới dạng mã Swift. Động lực chính là mã C ++ đại diện cho một thư viện cốt lõi được sử dụng trên nhiều nền tảng. Thực sự, tôi chỉ đang tạo một giao diện người dùng dựa trên Swift để cho phép chức năng cốt lõi hoạt động trong OS X.

Có những câu hỏi khác đặt ra, "Làm cách nào để gọi một hàm C ++ từ Swift." Đây không phải là câu hỏi của tôi. Để kết nối với một hàm C ++, cách sau hoạt động tốt:

Xác định tiêu đề bắc cầu thông qua "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 */

Mã Swift hiện có thể gọi các hàm trực tiếp

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

Câu hỏi của tôi là cụ thể hơn. Làm cách nào để tôi có thể khởi tạo và thao tác một Lớp C ++ từ bên trong Swift? Tôi dường như không thể tìm thấy bất cứ điều gì được công bố về điều này.


8
Cá nhân tôi đã sử dụng đến việc viết các tệp trình bao bọc Objective-C ++ đơn giản để hiển thị một lớp Objective-C tái tạo tất cả các lệnh gọi C ++ có liên quan và chỉ cần chuyển tiếp chúng đến một phiên bản được lưu giữ của lớp C ++. Trong trường hợp của tôi, số lượng các lớp và lệnh gọi C ++ ít nên nó không đặc biệt tốn nhiều công sức. Nhưng tôi sẽ tiếp tục ủng hộ điều này như một câu trả lời với hy vọng rằng ai đó sẽ nghĩ ra một cái gì đó tốt hơn.
Tommy

1
Vâng, đó là một cái gì đó ... Chúng ta hãy chờ xem (và hy vọng).
David Hoelzer

Tôi đã nhận được một đề xuất qua IRC để viết một lớp trình bao bọc Swift duy trì một con trỏ void đến đối tượng C ++ thực tế và hiển thị các phương thức được yêu cầu, một cách hiệu quả, chỉ được truyền qua C bridge và con trỏ tới đối tượng.
David Hoelzer

4
Như một bản cập nhật cho điều này, Swift 3.0 hiện đã ra mắt và, bất chấp những lời hứa trước đó, khả năng tương tác C ++ hiện được đánh dấu là "Ngoài phạm vi".
David Hoelzer,

Câu trả lời:


61

Tôi đã tìm ra một câu trả lời hoàn toàn có thể quản lý được. Bạn muốn điều này sạch sẽ như thế nào hoàn toàn dựa trên mức độ công việc bạn sẵn sàng làm.

Đầu tiên, lấy lớp C ++ của bạn và tạo các hàm C "wrapper" để giao tiếp với nó. Ví dụ: nếu chúng ta có lớp C ++ này:

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

Sau đó, chúng tôi triển khai các hàm C ++ sau:

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

Sau đó, tiêu đề cầu nối chứa:

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

Từ Swift, bây giờ chúng ta có thể khởi tạo đối tượng và tương tác với nó như sau:

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!

Vì vậy, như bạn có thể thấy, giải pháp (thực ra khá đơn giản) là tạo các trình bao bọc sẽ khởi tạo một đối tượng và trả về một con trỏ cho đối tượng đó. Điều này sau đó có thể được chuyển trở lại các hàm wrapper, có thể dễ dàng coi nó như một đối tượng phù hợp với lớp đó và gọi các hàm thành viên.

Làm cho nó sạch hơn

Mặc dù đây là một khởi đầu tuyệt vời và chứng minh rằng việc sử dụng các lớp C ++ hiện có với một cầu nối nhỏ là hoàn toàn khả thi, nó thậm chí có thể sạch hơn.

Làm sạch điều này đơn giản có nghĩa là chúng tôi loại bỏ UnsafeMutablePointer<Void>phần giữa mã Swift của chúng tôi và đóng gói nó thành một lớp Swift. Về cơ bản, chúng tôi sử dụng các hàm trình bao bọc C / C ++ tương tự nhưng giao diện chúng với một lớp Swift. Lớp Swift duy trì tham chiếu đối tượng và về cơ bản chỉ chuyển tất cả các lệnh gọi tham chiếu phương thức và thuộc tính thông qua cầu nối đến đối tượng C ++!

Sau khi thực hiện điều này, tất cả mã bắc cầu hoàn toàn được gói gọn trong lớp Swift. Mặc dù chúng ta vẫn đang sử dụng C bridge, chúng ta đang sử dụng hiệu quả các đối tượng C ++ một cách minh bạch mà không cần phải giải mã chúng trong Objective-C hoặc Objective-C ++.


12
Có một số vấn đề quan trọng bị bỏ lỡ ở đây. Bản thể đầu tiên mà hàm hủy không bao giờ được gọi, và đối tượng bị rò rỉ. Thứ hai là các ngoại lệ có thể gây ra một số rắc rối khó chịu.
Michael Anderson

2
Tôi đã đề cập đến một loạt các vấn đề trong câu trả lời của mình cho câu hỏi này stackoverflow.com/questions/2045774/…
Michael Anderson

2
@DavidHoelzer, tôi chỉ muốn nhấn mạnh ý tưởng này vì một số nhà phát triển sẽ cố gắng gói toàn bộ thư viện C ++ và sử dụng nó như nó được viết bằng Swift. Bạn đã đưa ra một ví dụ tuyệt vời về cách "khởi tạo và thao tác một Lớp C ++ từ bên trong Swift"! Và tôi chỉ muốn nói thêm rằng kỹ thuật này nên được sử dụng một cách thận trọng! Cảm ơn bạn cho ví dụ của bạn!
Nicolai Nita

1
Đừng nghĩ rằng điều này là "hoàn hảo". Tôi nghĩ nó gần giống như bạn viết một trình bao bọc Objective-C rồi sử dụng nó trong Swift.
Bagusflyer

1
@Bagusflyer chắc chắn ... ngoại trừ việc nó hoàn toàn không phải là một trình bao bọc vật kính-C.
David Hoelzer

11

Swift hiện không có tương tác C ++. Đó là một mục tiêu dài hạn, nhưng rất khó xảy ra trong tương lai gần.


25
Gọi "sử dụng trình bao bọc C" một dạng "tương tác C ++" đang kéo dài thuật ngữ này khá nhiều
Catfish_Man

6
Có lẽ, nhưng việc sử dụng chúng để cho phép bạn khởi tạo một lớp C ++ và sau đó gọi các phương thức dựa trên nó làm cho nó hữu ích và đáp ứng câu hỏi.
David Hoelzer

2
Trên thực tế, đó không còn là mục tiêu dài hạn mà thay vào đó, nó được đánh dấu là "Ngoài phạm vi" trong lộ trình.
David Hoelzer

4
sử dụng c-wrappers là cách tiếp cận tiêu chuẩn cho hầu hết C ++ interop sang ngôn ngữ bất kỳ, python, java vv
Andrew Paul Simmons

8

Ngoài giải pháp của riêng bạn, có một cách khác để làm điều đó. Bạn có thể gọi hoặc viết trực tiếp mã C ++ trong mục tiêu-c ++.

Vì vậy, bạn có thể tạo một trình bao bọc mục tiêu-C ++ trên đầu mã C ++ của mình và tạo một giao diện phù hợp.

Sau đó, gọi mã mục tiêu-C ++ từ mã nhanh của bạn. Để có thể viết mã mục tiêu-C ++, bạn có thể phải đổi tên phần mở rộng tệp từ .m thành .mm

Đừng quên giải phóng bộ nhớ được cấp phát bởi các đối tượng C ++ của bạn khi phù hợp.


6

Như một câu trả lời khác đã đề cập, việc sử dụng ObjC ++ để tương tác dễ dàng hơn nhiều. Chỉ cần đặt tên tệp của bạn .mm thay vì .m và xcode / clang, cho phép bạn truy cập vào c ++ trong tệp đó.

Lưu ý rằng ObjC ++ không hỗ trợ kế thừa C ++. Tôi bạn muốn phân lớp một lớp c ++ trong objC ++, bạn không thể. Bạn sẽ phải viết lớp con bằng C ++ và bọc nó xung quanh một lớp objC ++.

Sau đó, sử dụng tiêu đề bắc cầu mà bạn thường sử dụng để gọi objc từ swift.


2
Bạn có thể đã bỏ lỡ điểm. Giao diện là bắt buộc vì chúng tôi có một ứng dụng C ++ đa nền tảng rất lớn mà chúng tôi hiện đang hỗ trợ trên OS X. Objective C ++ thực sự không phải là một lựa chọn cho toàn bộ dự án.
David Hoelzer

Tôi không chắc tôi hiểu bạn. Bạn chỉ cần ObjC ++ khi bạn muốn một cuộc gọi đi giữa swift và c ++ hoặc ngược lại. Chỉ các ranh giới cần được viết bằng objc ++, không phải toàn bộ dự án.
nnrales

1
Vâng, tôi đã xem qua câu hỏi của bạn. Có vẻ như bạn đang muốn thao tác trực tiếp với C ++ từ nhanh. Tôi không biết làm thế nào để làm điều đó.
nnrales

1
Đó là những gì câu trả lời đã đăng của tôi đạt được. Các hàm C cho phép bạn chuyển các đối tượng vào và ra dưới dạng các khối bộ nhớ tùy ý và gọi các phương thức ở hai bên.
David Hoelzer

1
@DavidHoelzer Nó nghĩ là tuyệt, nhưng bạn không làm cho mã phức tạp hơn theo cách này sao? Tôi đoán nó là chủ quan. Đối với tôi, trình bao bọc của ObjC ++ có vẻ sạch hơn.
nnrales

6

Bạn có thể sử dụng Scapix Language Bridge để tự động kết nối C ++ với Swift (trong số các ngôn ngữ khác). Mã cầu nối được tạo tự động trực tiếp từ các tệp tiêu đề C ++. Đây là một ví dụ :

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

Nhanh:

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

        contact.sendMessage("Hello", friend)
        contact.addTags(["a","b","c"])
        contact.addFriends([friend])
    }
}

Hấp dẫn. Có vẻ như bạn vừa phát hành phần mềm này lần đầu tiên vào tháng 3 năm 2019.
David Hoelzer

@ boris-rasin Tôi không thể tìm thấy c ++ trực tiếp cho cầu nhanh trong ví dụ. Bạn có tính năng này trong kế hoạch không?
sage444

2
Có, không có cầu nối C ++ trực tiếp với Swift vào lúc này. Nhưng cầu nối C ++ với ObjC hoạt động với Swift hoàn toàn tốt (thông qua cầu nối objC với Swift của Apple). Tôi đang làm việc trên cầu trực tiếp ngay bây giờ (nó sẽ mang lại hiệu suất tốt hơn trong một số trường hợp).
Boris Rasin

1
Ồ vâng, bạn hoàn toàn đúng, nhưng trong dự án nhanh chóng trên linux thì không phải vậy. Nhìn về phía trước để xem xét việc thực hiện của bạn.
sage444
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.