Làm thế nào để gọi hàm C ++ từ C?


84

Tôi biết cái này.

Gọi hàm C từ C ++:

Nếu ứng dụng của tôi bằng C ++ và tôi phải gọi các hàm từ thư viện được viết bằng C. Thì tôi đã sử dụng

//main.cpp

extern "C" void C_library_function(int x, int y);//prototype
C_library_function(2,4);// directly using it.

Điều này sẽ không làm sai lệch tên C_library_functionvà trình liên kết sẽ tìm thấy cùng một tên trong các tệp * .lib đầu vào của nó và vấn đề đã được giải quyết.

Gọi hàm C ++ từ C ???

Nhưng ở đây tôi đang mở rộng một ứng dụng lớn được viết bằng C và tôi cần sử dụng một thư viện được viết bằng C ++. Tên của C ++ đang gây ra rắc rối ở đây. Linker đang phàn nàn về các ký hiệu chưa được giải quyết. Tôi không thể sử dụng trình biên dịch C ++ cho dự án C của mình vì điều đó sẽ phá vỡ nhiều thứ khác. đường ra là gì?

Nhân tiện tôi đang sử dụng MSVC




2
Có thể trùng lặp của nhã gọi C ++ từ C
user2284570

Câu trả lời:


95

Bạn cần tạo API C để hiển thị chức năng của mã C ++ của bạn. Về cơ bản, bạn sẽ cần viết mã C ++ được khai báo là "C" bên ngoài và có API C thuần túy (ví dụ: không sử dụng các lớp) bao bọc thư viện C ++. Sau đó, bạn sử dụng thư viện trình bao bọc C thuần túy mà bạn đã tạo.

API C của bạn có thể tùy chọn theo kiểu hướng đối tượng, mặc dù C không phải là hướng đối tượng. Ví dụ:

 // *.h file
 // ...
 #ifdef __cplusplus
 #define EXTERNC extern "C"
 #else
 #define EXTERNC
 #endif

 typedef void* mylibrary_mytype_t;

 EXTERNC mylibrary_mytype_t mylibrary_mytype_init();
 EXTERNC void mylibrary_mytype_destroy(mylibrary_mytype_t mytype);
 EXTERNC void mylibrary_mytype_doit(mylibrary_mytype_t self, int param);

 #undef EXTERNC
 // ...


 // *.cpp file
 mylibrary_mytype_t mylibrary_mytype_init() {
   return new MyType;
 }

 void mylibrary_mytype_destroy(mylibrary_mytype_t untyped_ptr) {
    MyType* typed_ptr = static_cast<MyType*>(untyped_ptr);
    delete typed_ptr;
 }

 void mylibrary_mytype_doit(mylibrary_mytype_t untyped_self, int param) {
    MyType* typed_self = static_cast<MyType*>(untyped_self);
    typed_self->doIt(param);
 }


1
Làm thế nào để bạn liên kết tệp thực thi cuối cùng? Bạn sử dụng C hay C ++? (Khi tôi sử dụng C, trình liên kết không thể tìm thấy hàm C ++; khi tôi sử dụng C ++, trình liên kết không thể tìm thấy std :: cout.)
Zack

1
@Zack nếu bạn tạo một thư viện tĩnh với trình biên dịch / liên kết C ++ bao gồm tạm thời các phần phụ thuộc của nó (bao gồm cả thư viện chuẩn C ++, có thể được liên kết ngầm bởi trình liên kết của bạn), bạn sẽ có thể liên kết thư viện tĩnh đó với mã C bằng cách sử dụng Trình biên dịch / liên kết C.
Michael Aaron Safyan

Bạn có thể xuất một mẫu từ C ++ sang C không?
MarcusJ

Tôi có hành vi "kết hợp" vì tôi cần thư viện của mình được gọi bằng cả mã C ++ và C. Trong hộp, *.cpp filetôi cũng cần phải bọc các hàm thành #ifdef _cplusplus+ extern "C"để tránh lỗi "tham chiếu không xác định tới" tại thời điểm liên kết.
Coconop

41

Tôi sẽ làm điều đó theo cách sau:

(Nếu làm việc với MSVC, hãy bỏ qua các lệnh biên dịch GCC)

Giả sử rằng tôi có một lớp C ++ có tên AAA , được định nghĩa trong các tệp aaa.h, aaa.cpp và lớp AAA có một phương thức có tên sayHi (const char * name) mà tôi muốn bật cho mã C.

Mã C ++ của lớp AAA - C ++ thuần túy, tôi không sửa đổi nó:

aaa.h

#ifndef AAA_H
#define AAA_H

class AAA {
    public:
        AAA();
        void sayHi(const char *name);
};

#endif

aaa.cpp

#include <iostream>

#include "aaa.h"

AAA::AAA() {
}

void AAA::sayHi(const char *name) {
    std::cout << "Hi " << name << std::endl;
}

Biên dịch lớp này như thường xuyên được thực hiện cho C ++. Mã này "không biết" rằng nó sẽ được sử dụng bởi mã C. Sử dụng lệnh:

g++ -fpic -shared aaa.cpp -o libaaa.so

Bây giờ, cũng trong C ++, tạo một trình kết nối C. Định nghĩa nó trong các tệp aaa_c_connector.h, aaa_c_connector.cpp . Trình kết nối này sẽ xác định một hàm C, có tên AAA_sayHi (cosnt char * name) , sẽ sử dụng một thể hiện của AAA và sẽ gọi phương thức của nó:

aaa_c_connector.h

#ifndef AAA_C_CONNECTOR_H 
#define AAA_C_CONNECTOR_H 

#ifdef __cplusplus
extern "C" {
#endif

void AAA_sayHi(const char *name);

#ifdef __cplusplus
}
#endif


#endif

aaa_c_connector.cpp

#include <cstdlib>

#include "aaa_c_connector.h"
#include "aaa.h"

#ifdef __cplusplus
extern "C" {
#endif

// Inside this "extern C" block, I can implement functions in C++, which will externally 
//   appear as C functions (which means that the function IDs will be their names, unlike
//   the regular C++ behavior, which allows defining multiple functions with the same name
//   (overloading) and hence uses function signature hashing to enforce unique IDs),


static AAA *AAA_instance = NULL;

void lazyAAA() {
    if (AAA_instance == NULL) {
        AAA_instance = new AAA();
    }
}

void AAA_sayHi(const char *name) {
    lazyAAA();
    AAA_instance->sayHi(name);
}

#ifdef __cplusplus
}
#endif

Biên dịch nó, một lần nữa, sử dụng lệnh biên dịch C ++ thông thường:

g++ -fpic -shared aaa_c_connector.cpp -L. -laaa -o libaaa_c_connector.so

Bây giờ tôi có một thư viện được chia sẻ (libaaa_c_connector.so), thực hiện hàm C AAA_sayHi (const char * name) . Bây giờ tôi có thể tạo một tệp C chính và biên dịch tất cả lại với nhau:

main.c

#include "aaa_c_connector.h"

int main() {
    AAA_sayHi("David");
    AAA_sayHi("James");

    return 0;
}

Biên dịch nó bằng lệnh biên dịch C:

gcc main.c -L. -laaa_c_connector -o c_aaa

Tôi sẽ cần đặt LD_LIBRARY_PATH để chứa $ PWD và nếu tôi chạy tệp thực thi ./c_aaa , tôi sẽ nhận được đầu ra mà tôi mong đợi:

Hi David
Hi James

BIÊN TẬP:

Trên một số bản phân phối linux, -laaa-lstdc++cũng có thể được yêu cầu cho lệnh biên dịch cuối cùng. Cảm ơn @AlaaM. cho sự chú ý


1
bạn cũng có thể chỉ định đường dẫn liên kết lib vớigcc usecpp.c -L. -laaa_c_connector -Wl,-rpath,. -o c_aaa
Metaphox

những gì usecpp.c?
Alaa M.

1
Dòng biên soạn cuối cùng sẽ là: gcc main.c -L. -laaa_c_connector -laaa -lstdc++ -o c_aaa. Lưu ý -laaa-lstdc+++
Alaa M.

@AlaaM. ai đó đã chỉnh sửa mã của tôi và thay đổi main.cthành usecpp.c. Tôi vừa hoàn nguyên nó .... Về nhận xét khác, nó cũng sẽ hoạt động như vậy. Việc sử dụng biên soạn thứ hai -laaavà nó có lẽ nên được đủ (Tôi đã viết nó hơn 1,5 năm trước, tôi không nhớ rất rõ những gì tôi đã làm, nhưng tôi nghĩ rằng tôi đã kiểm tra tất cả các dòng trước khi gửi bài)
SomethingSomething

1
Ok, đã thêm nó như một ghi chú ở cuối bài viết. Cảm ơn vì đã chú ý!
SomethingSomething

6

Bạn sẽ phải viết một trình bao bọc cho C trong C ++ nếu bạn muốn làm điều này. C ++ tương thích ngược, nhưng C không tương thích ngược.


5

Giả sử API C ++ tương thích với C (không có lớp, mẫu, v.v.), bạn có thể bọc nó lại extern "C" { ... }, giống như cách bạn đã làm khi làm theo cách khác.

Nếu bạn muốn hiển thị các đối tượng và những thứ C ++ dễ thương khác, bạn sẽ phải viết một API trình bao bọc.


Không hoàn toàn ... thư viện C ++ sẽ cần được biên dịch lại.
Michael Aaron Safyan

Ôi không. C ++ API hoàn toàn hướng đối tượng.
vuốt vào

2
@claws, hãy xem bài viết của tôi về cách tạo mã C kiểu OOP .. và tạo thư viện trình bao bọc bằng cách sử dụng kiểu đó với giao diện C, nhưng triển khai C ++ bên dưới. Sau đó liên kết đến giao diện C.
Michael Aaron Safyan

Bạn có thể muốn có một cái nhìn tại 'công cụ' wrapper như uống một lân, pyboost, ... đó làm công cụ tương đương (nhưng không phải đối với C ...)
xtofl

2

xuất các hàm C ++ của bạn dưới dạng extern "C" (hay còn gọi là ký hiệu kiểu C) hoặc sử dụng định dạng tệp .def để xác định các ký hiệu xuất chưa được trang trí cho trình liên kết C ++ khi nó tạo thư viện C ++, khi đó trình liên kết C sẽ không gặp khó khăn khi đọc nó


0
#include <iostream>

//////////////
// C++ code //
//////////////
struct A
{
  int i;
  int j;

  A() {i=1; j=2; std::cout << "class A created\n";}
  void dump() {std::cout << "class A dumped: " << i << ":" << j << std::endl;}
  ~A() {std::cout << "class A destroyed\n";}
};

extern "C" {
  // this is the C code interface to the class A
  static void *createA (void)
  {
    // create a handle to the A class
    return (void *)(new A);
  }
  static void dumpA (void *thisPtr)
  {
    // call A->dump ()
    if (thisPtr != NULL) // I'm an anal retentive programmer
    {
      A *classPtr = static_cast<A *>(thisPtr);
      classPtr->dump ();
    }
  }
  static void *deleteA (void *thisPtr)
  {
    // destroy the A class
    if (thisPtr != NULL)
    {
      delete (static_cast<A *>(thisPtr));
    }
  }
}

////////////////////////////////////
// this can be compiled as C code //
////////////////////////////////////
int main (int argc, char **argv)
{
  void *handle = createA();

  dumpA (handle);
  deleteA (handle);

  return 0;
}

2
Vui lòng xem xét thêm một số giải thích cho câu trả lời của bạn.
HMD

Giải thích ngắn gọn về cách trả lời câu hỏi ở trên làm cho câu trả lời hữu ích hơn cho người khác.
SOS

-3

Bạn có thể đặt tiền tố khai báo hàm bằng từ khóa extern “C”, ví dụ:

extern “C” int Mycpp Chức năng ()

{

// Mã ở đây

trả về 0;

}

Để biết thêm ví dụ, bạn có thể tìm kiếm thêm trên Google về từ khóa “extern”. Bạn cần phải làm thêm một số việc, nhưng không khó, bạn sẽ nhận được rất nhiều ví dụ từ Google.


Ba phiếu phản đối và không có một lời giải thích nào. Vẫn tuyệt vời như mọi khi, cộng đồng SO.
Zimano
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.