Phát triển API trình bao bọc C cho mã C ++ hướng đối tượng


81

Tôi đang tìm cách phát triển một bộ C API sẽ bao quanh các API C ++ hiện có của chúng tôi để truy cập logic cốt lõi của chúng tôi (được viết bằng C ++ hướng đối tượng). Về cơ bản, đây sẽ là một API keo cho phép logic C ++ của chúng ta có thể sử dụng được bởi các ngôn ngữ khác. Một số hướng dẫn, sách hoặc phương pháp hay nhất giới thiệu các khái niệm liên quan đến việc bao bọc C xung quanh C ++ hướng đối tượng là gì?


4
kiểm tra nguồn zeromq để lấy cảm hứng. Thư viện hiện được viết bằng C ++ và có các ràng buộc C. zeromq.org
Hassan Syed

1
Có liên quan (hoặc thậm chí là bản sao): Gói API lớp C ++ cho tiêu thụ C
người dùng

Câu trả lời:


69

Điều này không quá khó để làm bằng tay, nhưng sẽ phụ thuộc vào kích thước giao diện của bạn. Các trường hợp mà tôi đã làm là cho phép sử dụng thư viện C ++ của chúng tôi từ bên trong mã C thuần túy, và do đó SWIG không giúp được gì nhiều. (Chà có thể SWIG có thể được sử dụng để làm điều này, nhưng tôi không phải là chuyên gia SWIG và nó có vẻ không tầm thường)

Tất cả những gì chúng tôi đã làm là:

  1. Mọi đối tượng được chuyển về trong C một tay cầm không trong suốt.
  2. Bộ tạo và bộ hủy được bao bọc trong các chức năng thuần túy
  3. Các hàm thành viên là các hàm thuần túy.
  4. Các nội trang khác được ánh xạ tới C tương đương nếu có thể.

Vì vậy, một lớp như thế này (tiêu đề C ++)

class MyClass
{
  public:
  explicit MyClass( std::string & s );
  ~MyClass();
  int doSomething( int j );
}

Sẽ ánh xạ đến một giao diện C như thế này (tiêu đề C):

struct HMyClass; // An opaque type that we'll use as a handle
typedef struct HMyClass HMyClass;
HMyClass * myStruct_create( const char * s );
void myStruct_destroy( HMyClass * v );
int myStruct_doSomething( HMyClass * v, int i );

Việc triển khai giao diện sẽ như thế này (nguồn C ++)

#include "MyClass.h"

extern "C" 
{
  HMyClass * myStruct_create( const char * s )
  {
    return reinterpret_cast<HMyClass*>( new MyClass( s ) );
  }
  void myStruct_destroy( HMyClass * v )
  {
    delete reinterpret_cast<MyClass*>(v);
  }
  int myStruct_doSomething( HMyClass * v, int i )
  {
    return reinterpret_cast<MyClass*>(v)->doSomething(i);
  }
}

Chúng tôi lấy bộ xử lý không rõ ràng của mình từ lớp gốc để tránh cần bất kỳ quá trình truyền nào và (Điều này dường như không hoạt động với trình biên dịch hiện tại của tôi). Chúng ta phải làm cho xử lý là một cấu trúc vì C không hỗ trợ các lớp.

Vì vậy, điều đó cho chúng ta giao diện C cơ bản. Nếu bạn muốn có một ví dụ hoàn chỉnh hơn cho thấy một cách mà bạn có thể tích hợp xử lý ngoại lệ, thì bạn có thể thử mã của tôi trên github: https://gist.github.com/mikeando/5394166

Phần thú vị bây giờ là đảm bảo rằng bạn nhận được tất cả các thư viện C ++ cần thiết được liên kết vào thư viện lớn hơn của bạn một cách chính xác. Đối với gcc (hoặc clang) có nghĩa là chỉ thực hiện giai đoạn liên kết cuối cùng bằng cách sử dụng g ++.


11
Tôi khuyên bạn nên sử dụng thứ gì đó khác với void, ví dụ như một cấu trúc ẩn danh thay vì void * cho đối tượng được trả về. Điều này có thể cung cấp một số loại an toàn cho các tay cầm trả về. Hãy xem stackoverflow.com/questions/839765/… để biết thêm thông tin về nó.
Laserallan

3
Tôi đồng ý với Laserallan và đã cấu trúc lại mã của mình cho phù hợp
Michael Anderson

2
@Mike Weller Việc mới và xóa bên trong khối "C" bên ngoài là được. Chữ "C" bên ngoài chỉ ảnh hưởng đến tên gọi. Trình biên dịch C không bao giờ nhìn thấy tệp đó, chỉ có tiêu đề.
Michael Anderson

2
Tôi cũng bỏ lỡ một typedef cần thiết để làm cho tất cả được biên dịch trong C. Cấu trúc typdef kỳ lạ Foo Foo; "gian lận". Mã được cập nhật
Michael Anderson

5
@MichaelAnderson, có hai lỗi chính tả trong hàm myStruct_destroyvà của bạn myStruct_doSomething. Nên reinterpret_cast<MyClass*>(v).
firegurafiku

17

Tôi nghĩ câu trả lời của Michael Anderson đang đi đúng hướng nhưng cách tiếp cận của tôi sẽ khác. Bạn phải lo lắng về một điều nữa: Ngoại lệ. Các ngoại lệ không phải là một phần của C ABI nên bạn không thể để các Ngoại lệ bị ném qua mã C ++. Vì vậy, tiêu đề của bạn sẽ trông như thế này:

#ifdef __cplusplus
extern "C"
{
#endif
    void * myStruct_create( const char * s );
    void myStruct_destroy( void * v );
    int myStruct_doSomething( void * v, int i );
#ifdef __cplusplus
}
#endif

Và tệp .cpp của trình bao bọc của bạn sẽ trông như thế này:

void * myStruct_create( const char * s ) {
    MyStruct * ms = NULL;
    try { /* The constructor for std::string may throw */
        ms = new MyStruct(s);
    } catch (...) {}
    return static_cast<void*>( ms );
}

void myStruct_destroy( void * v ) {
    MyStruct * ms = static_cast<MyStruct*>(v);
    delete ms;
}

int myStruct_doSomething( void * v, int i ) {
    MyStruct * ms = static_cast<MyStruct*>(v);
    int ret_value = -1; /* Assuming that a negative value means error */
    try {
        ret_value = ms->doSomething(i);
    } catch (...) {}
    return ret_value;
}

Tốt hơn nữa: Nếu bạn biết rằng tất cả những gì bạn cần chỉ là một phiên bản MyStruct, đừng mạo hiểm với việc xử lý các con trỏ void được chuyển đến API của bạn. Thay vào đó hãy làm điều gì đó như thế này:

static MyStruct * _ms = NULL;

int myStruct_create( const char * s ) {
    int ret_value = -1; /* error */
    try { /* The constructor for std::string may throw */
        _ms = new MyStruct(s);
        ret_value = 0; /* success */
    } catch (...) {}
    return ret_value;
}

void myStruct_destroy() {
    if (_ms != NULL) {
        delete _ms;
    }
}

int myStruct_doSomething( int i ) {
    int ret_value = -1; /* Assuming that a negative value means error */
    if (_ms != NULL) {
        try {
            ret_value = _ms->doSomething(i);
        } catch (...) {}
    }
    return ret_value;
}

API này an toàn hơn rất nhiều.

Nhưng, như Michael đã đề cập, việc liên kết có thể trở nên khá phức tạp.

Hi vọng điêu nay co ich


2
Để biết thêm về cách xử lý ngoại lệ cho trường hợp này, hãy xem chuỗi sau: stackoverflow.com/questions/847279/…
Laserallan

2
Khi tôi biết thư viện C ++ của mình cũng sẽ có API C, tôi đóng gói mã lỗi API int bên trong lớp cơ sở ngoại lệ của mình. Sẽ dễ dàng hơn khi biết tình trạng lỗi chính xác tại trang web và cung cấp mã lỗi rất cụ thể. "Trình bao bọc" try-catch trong các hàm API C bên ngoài chỉ cần truy xuất mã lỗi và trả lại cho người gọi. Đối với các ngoại lệ thư viện tiêu chuẩn khác, hãy tham khảo liên kết của Laserallan.
Emile Cormier

2
catch (...) {} là một cái ác thuần túy không pha tạp. Điều hối tiếc duy nhất của tôi là tôi chỉ có thể bỏ phiếu một lần.
Terry Mahaffey

2
@Terry Mahaffey Tôi hoàn toàn đồng ý với bạn rằng điều đó là xấu xa. Tốt nhất là làm những gì Emile đã đề nghị. Nhưng nếu bạn phải đảm bảo rằng mã được bọc sẽ không bao giờ ném ra, bạn không có lựa chọn nào khác ngoài việc đặt một chốt bắt (...) ở dưới cùng của tất cả các lệnh bắt khác được xác định. Trường hợp này xảy ra vì thư viện bạn đang đóng gói có thể được lưu trữ kém. Không có cấu trúc C ++ nào mà bạn có thể sử dụng để thực thi rằng chỉ một tập hợp các ngoại lệ có thể được ném ra. Ít hơn của hai tệ nạn là gì? bắt (...) hoặc có nguy cơ gặp sự cố thời gian chạy khi mã được bọc cố gắng chuyển đến trình gọi C?
figurassa

1
bắt (...) {std :: end (); } là chấp nhận được. catch (...) {} là một lỗ hổng bảo mật tiềm năng
Terry Mahaffey

10

Không khó để hiển thị mã C ++ sang C, chỉ cần sử dụng mẫu thiết kế Mặt tiền

Tôi giả sử mã C ++ của bạn được xây dựng trong một thư viện, tất cả những gì bạn cần làm là tạo một mô-đun C trong thư viện C ++ của bạn làm Mặt tiền cho thư viện của bạn cùng với tệp tiêu đề C thuần túy. Mô-đun C sẽ gọi các hàm C ++ có liên quan

Sau khi bạn làm điều đó, các ứng dụng và thư viện C của bạn sẽ có toàn quyền truy cập vào api C mà bạn đã tiếp xúc.

ví dụ: đây là một mô-đun Mặt tiền mẫu

#include <libInterface.h>
#include <objectedOrientedCppStuff.h>

int doObjectOrientedStuff(int *arg1, int arg2, char *arg3) {
      Object obj = ObjectFactory->makeCppObj(arg3); // doing object oriented stuff here
      obj->doStuff(arg2);
      return obj->doMoreStuff(arg1);
   }

sau đó bạn hiển thị hàm C này làm API của mình và bạn có thể sử dụng nó một cách thoải mái như một lib C mà không phải lo lắng về

// file name "libIntrface.h"
extern int doObjectOrientedStuff(int *, int, char*);

Rõ ràng đây là một ví dụ giả nhưng đây là cách dễ nhất để giải thích thư viện C ++ sang C


Xin chào @hhafez, bạn có một ví dụ đơn giản về hello world không? Một với chuỗi?
Jason Foglia

cho một chàng trai cpp phi này là đáng yêu
Nicklas Aven

6

Tôi nghĩ rằng bạn có thể có được một số ý tưởng về hướng đi và / hoặc có thể sử dụng trực tiếp SWIG . Tôi nghĩ rằng việc xem qua một vài ví dụ ít nhất sẽ cung cấp cho bạn ý tưởng về những loại điều cần xem xét khi gói một API này vào một API khác. Bài tập có thể có lợi.

SWIG là một công cụ phát triển phần mềm kết nối các chương trình được viết bằng C và C ++ với nhiều ngôn ngữ lập trình cấp cao. SWIG được sử dụng với nhiều loại ngôn ngữ khác nhau bao gồm các ngôn ngữ kịch bản phổ biến như Perl, PHP, Python, Tcl và Ruby. Danh sách các ngôn ngữ được hỗ trợ cũng bao gồm các ngôn ngữ không phải tập lệnh như C #, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Lua, Modula-3, OCAML, Octave và R. Ngoài ra, một số triển khai Đề án được thông dịch và biên dịch ( Guile, MzScheme, Chicken) được hỗ trợ. SWIG được sử dụng phổ biến nhất để tạo môi trường lập trình được thông dịch hoặc biên dịch ở mức cao, giao diện người dùng và như một công cụ để kiểm tra và tạo mẫu phần mềm C / C ++. SWIG cũng có thể xuất cây phân tích cú pháp của nó dưới dạng biểu thức s-XML và Lisp. SWIG có thể được sử dụng, phân phối tự do,


2
SWIG là chỉ hơn kill, nếu tất cả các ông muốn làm là để tạo ra một thư viện C ++ có sẵn từ C.
hhafez

1
Đó là một ý kiến ​​và không có phản hồi thực sự hữu ích. SWIG sẽ hữu ích nếu mã gốc là: Thay đổi nhanh chóng, Không có tài nguyên C ++ để duy trì nó và chỉ có tài nguyên C và nếu nhà phát triển muốn tự động tạo C API. Đây là những lý do phổ biến và chắc chắn hợp lệ để sử dụng SWIG.
user1363990

5

Chỉ cần thay thế khái niệm đối tượng bằng a void *(thường được gọi là kiểu mờ trong thư viện hướng C) và sử dụng lại mọi thứ bạn biết từ C ++.


2

Tôi nghĩ rằng sử dụng SWIG là câu trả lời tốt nhất ... nó không chỉ tránh được việc phát minh lại bánh xe mà còn đáng tin cậy và còn thúc đẩy sự phát triển liên tục hơn là chỉ cố gắng giải quyết vấn đề.

Các vấn đề về tần số cao cần được giải quyết bằng một giải pháp lâu dài.

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.