Jak wywołać funkcję C ++ z C?


84

Wiem to.

Wywołanie funkcji C z C ++:

Gdyby moja aplikacja była w C ++ i musiałbym wywołać funkcje z biblioteki napisanej w C. Wtedy bym użył

//main.cpp

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

Nie zmieniłoby to nazwy, C_library_functiona konsolidator znalazłby tę samą nazwę w swoich wejściowych plikach * .lib i problem został rozwiązany.

Wywołanie funkcji C ++ z C ???

Ale tutaj rozszerzam dużą aplikację napisaną w C i potrzebuję biblioteki napisanej w C ++. Zniekształcanie nazw w C ++ powoduje tutaj problemy. Linker narzeka na nierozwiązane symbole. Cóż, nie mogę użyć kompilatora C ++ w moim projekcie C, ponieważ to psuje wiele innych rzeczy. Jakie jest wyjście?

Przy okazji używam MSVC




Odpowiedzi:


95

Aby udostępnić funkcjonalność kodu w języku C ++, musisz utworzyć interfejs API języka C. Zasadniczo będziesz musiał napisać kod w C ++, który jest zadeklarowany jako extern "C" i który ma czysty interfejs API języka C (na przykład nie używający klas), który otacza bibliotekę C ++. Następnie używasz czystej biblioteki C wrapper, którą stworzyłeś.

Twój interfejs API języka C może opcjonalnie być zgodny ze stylem zorientowanym obiektowo, nawet jeśli język C nie jest zorientowany obiektowo. Dawny:

 // *.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
Jak połączyć ostateczny plik wykonywalny? Używasz C czy C ++? (Kiedy użyłem C, linker nie mógł znaleźć funkcji C ++; kiedy użyłem C ++, linker nie mógł znaleźć std :: cout.)
Zack

1
@Zack Jeśli utworzysz bibliotekę statyczną za pomocą kompilatora / konsolidatora C ++, który przejściowo zawiera jej zależności (w tym bibliotekę standardową C ++, która może być niejawnie połączona przez twój linker), powinieneś być w stanie połączyć tę bibliotekę statyczną z kodem C za pomocą Kompilator / konsolidator języka C.
Michael Aaron Safyan

Czy możesz wyeksportować szablon z C ++ do C?
MarcusJ

Mam zachowanie „hybrydowe”, ponieważ potrzebuję, aby moja biblioteka była wywoływana zarówno przez kod C ++, jak i C. W sekcji *.cpp filemusiałem również zawinąć funkcje w #ifdef _cplusplusznak +, extern "C"aby uniknąć błędów „niezdefiniowanego odniesienia do” w czasie łączenia.
Coconop

41

Zrobiłbym to w następujący sposób:

(Jeśli pracujesz z MSVC, zignoruj ​​polecenia kompilacji GCC)

Załóżmy, że mam klasę C ++ o nazwie AAA , zdefiniowaną w plikach aaa.h, aaa.cpp i że klasa AAA ma metodę o nazwie sayHi (const char * name) , którą chcę włączyć dla kodu C.

Kod C ++ klasy AAA - Czysty C ++, nie modyfikuję go:

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

Kompilowanie tej klasy w sposób regularny dla C ++. Ten kod „nie wie”, że będzie używany przez kod C. Za pomocą polecenia:

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

Teraz, także w C ++, tworzenie łącznika C. Definiowanie go w plikach aaa_c_connector.h, aaa_c_connector.cpp . Ten łącznik zdefiniuje funkcję C o nazwie AAA_sayHi (cosnt char * name) , która będzie używać instancji AAA i wywoła swoją metodę:

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

Kompiluję go ponownie, używając zwykłego polecenia kompilacji C ++:

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

Teraz mam udostępnioną bibliotekę (libaaa_c_connector.so), która implementuje funkcję C AAA_sayHi (const char * name) . Mogę teraz utworzyć główny plik C i skompilować wszystko razem:

main.c

#include "aaa_c_connector.h"

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

    return 0;
}

Kompilowanie go za pomocą polecenia kompilacji w C:

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

Będę musiał ustawić LD_LIBRARY_PATH tak, aby zawierał $ PWD, a jeśli uruchomię plik wykonywalny ./c_aaa , otrzymam oczekiwany wynik:

Hi David
Hi James

EDYTOWAĆ:

W niektórych dystrybucjach Linuksa -laaai -lstdc++może być również wymagane dla ostatniego polecenia kompilacji. Dzięki @AlaaM. dla uwagi


1
możesz również określić ścieżkę łącza do biblioteki za pomocągcc usecpp.c -L. -laaa_c_connector -Wl,-rpath,. -o c_aaa
Metaphox

co jest usecpp.c?
Alaa M.

1
Ostatnia linia kompilacja powinna być: gcc main.c -L. -laaa_c_connector -laaa -lstdc++ -o c_aaa. Uwaga -laaai-lstdc+++
Alaa M.

@AlaaM. ktoś wyedytował mój kod i zmienił main.cna usecpp.c. Właśnie go cofnąłem ... Jeśli chodzi o drugi komentarz, powinien również działać tak, jak jest. Druga kompilacja używa -laaai chyba powinno wystarczyć (napisałem ją ponad 1,5 roku temu, nie pamiętam dobrze, co zrobiłem, ale chyba każdą linijkę przetestowałem przed wysłaniem)
SomethingSomething

1
Ok, dodałem to jako notatkę na końcu postu. Dziękuję za uwagę!
SomethingSomething

6

Będziesz musiał napisać opakowanie dla C w C ++, jeśli chcesz to zrobić. C ++ jest kompatybilny wstecz, ale C nie jest kompatybilny z poprzednimi wersjami.


5

Zakładając, że API C ++ jest kompatybilne z C (bez klas, szablonów itp.), Możesz je opakować extern "C" { ... }, tak jak robiłeś to w drugą stronę.

Jeśli chcesz udostępnić obiekty i inne urocze rzeczy w C ++, będziesz musiał napisać API wrappera.


Niezupełnie ... biblioteka C ++ musiałaby zostać ponownie skompilowana.
Michael Aaron Safyan

O nie. C ++ API jest w pełni zorientowane obiektowo.
pazury

2
@claws, zobacz mój artykuł o tworzeniu kodu C w stylu OOP ... i utwórz bibliotekę opakowującą przy użyciu tego stylu z interfejsem C, ale podstawową implementacją C ++. Następnie połącz się z interfejsem C.
Michael Aaron Safyan

Możesz rzucić okiem na „narzędzia” opakowujące, takie jak swig, pyboost, ... które robią podobne rzeczy (ale nie w kierunku C ...)
xtofl

2

wyeksportuj swoje funkcje C ++ jako zewnętrzne „C” (aka symbole w stylu C) lub użyj formatu pliku .def do zdefiniowania niezabezpieczonych symboli eksportu dla konsolidatora C ++ podczas tworzenia biblioteki C ++, wówczas konsolidator C nie powinien mieć problemów z odczytaniem tego


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
Proszę rozważyć dodanie wyjaśnienia do swoich odpowiedzi.
HMD

Krótkie wyjaśnienie, w jaki sposób powyższe odpowiedzi na pytanie sprawiają, że odpowiedź jest bardziej użyteczna dla innych.
SOS

-3

Deklarację funkcji można poprzedzić słowem kluczowym extern „C”, np

extern „C” int Mycppfunction ()

{

// Kod jest tutaj

return 0;

}

Więcej przykładów można znaleźć w Google na temat słowa kluczowego „extern”. Musisz zrobić jeszcze kilka rzeczy, ale nie jest to trudne, otrzymasz wiele przykładów z Google.


Trzy głosy przeciw i żadne wyjaśnienie. Bycie świetnym jak zawsze, społeczność SO.
Zimano
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.