Je crée des programmes en utilisant C. Cependant, je dois utiliser beaucoup de bibliothèques qui ont des API uniquement pour C++. Alors, est-il possible que je puisse créer un objet partagé en C++ puis accéder à ses fonctionnalités en utilisant C?
S'il n'est pas possible d'interfacer ces codes, comment puis-je obtenir des informations du code C++ au code C? J'ai essayé d'appeler des fonctions C++ à partir de C, mais j'obtiens des erreurs lors de la liaison lorsque j'inclus <string>
. Donc, lorsque j'appelle des fonctions C++ à partir de C, dois-je utiliser uniquement ce code qui sera compatible avec le compilateur C?
#ifndef CPPFILE_H
#define CPPFILE_H
#ifdef __cplusplus
extern "C" {
#endif
extern int myfunction(const char *filename);
#ifdef __cplusplus
}
#endif
#endif
#include "cppfile.hpp"
#include <string>
int myfunction(const char *filename) {
String S(filename);
return 0;
}
#include "cppfile.hpp"
int main(int argc, char **argv)
{
int i = myfunction(argv[1]);
printf("%d\n", i);
return 0;
}
gcc -c cmain.c
g++ -fPIC -shared -o cppfile.so cppfile.cpp
Vous voulez quelque chose de plus comme ça (et ici je vais utiliser un exemple un peu plus significatif):
#ifndef ANIMAL_H
#define ANIMAL_H
#ifdef __cplusplus
class Animal {
public:
Animal() : age(0), height(0) {}
Animal(int age, float height) : age(age), height(height) {}
virtual ~Animal() {}
int getAge();
void setAge(int new_age);
float getHeight();
void setHeight(float new_height);
private:
int age;
float height; // in metres!
};
#endif /* __cplusplus */
#ifdef __cplusplus
extern "C" {
#endif
struct animal; // a Nice opaque type
struct animal *animal_create();
struct animal *animal_create_init(int age, float height);
void animal_destroy(struct animal *a);
void animal_setage(struct animal *a, int new_age);
void animal_setheight(struct animal *a, float new_height);
int animal_getage(struct animal *a);
float animal_getheight(struct animal *a);
#ifdef __cplusplus
}
#endif
#endif /* ANIMAL_H */
#include "animal.h"
#define TO_CPP(a) (reinterpret_cast<Animal*>(a))
#define TO_C(a) (reinterpret_cast<animal*>(a))
void Animal::setAge(int new_age) { this->age = new_age; }
int Animal::getAge() { return this->age; }
void Animal::setHeight(float new_height) { this->height = new_height; }
float Animal::getHeight() { return this->height; }
animal *animal_create() {
animal *a = TO_C(new Animal);
return a;
}
animal *animal_create_init(int age, float height) {
animal *a = TO_C(new Animal(age, height));
return a;
}
void animal_destroy(animal *a) {
delete TO_CPP(a);
}
void animal_setage(animal *a, int new_age) {
TO_CPP(a)->setAge(new_age);
}
void animal_setheight(animal *a, float new_height) {
TO_CPP(a)->setHeight(new_height);
}
int animal_getage(animal *a) {
TO_CPP(a)->getAge();
}
float animal_getheight(animal *a) {
TO_CPP(a)->getHeight();
}
#include "animal.h"
#include <stdio.h>
int main()
{
// 6'0" 25yo (perhaps a human? :P)
struct animal *a = animal_create(25, 1.83);
animal_setage(a, 26); // birthday
printf("Age: %d\nHeight: %f", animal_getage(a), animal_getheight(a));
animal_destroy(a);
return 0;
}
#include "animal.h"
#include <iostream>
int main()
{
// 6'0" 25yo (perhaps a human? :P)
Animal* a = new Animal(25, 1.83);
a->setAge(26); // birthday
std::cout << "Age: " << a->getAge() << std::endl;
std::cout << "Height: " << a->getHeight();
delete a;
return 0;
}
Ainsi, lorsque vous compilez la bibliothèque, vous compilez animal.cpp
avec un compilateur C++. Vous pouvez ensuite le lier avec le code C et utiliser le animal_xxx
les fonctions.
Notez l'utilisation de struct animal
et Animal
. Animal
est un type C++ normal. C'est exactement à quoi ça ressemble. struct animal
, en revanche, est de type "opaque". Cela signifie que votre programme C peut voir qu'il est là, et peut en avoir un, mais il ne sait pas ce qu'il contient. Tout ce qu'il sait, c'est qu'il a une fonction qui prend un struct animal*
.
Dans une vraie bibliothèque, vous voudrez avoir des points de personnalisation pour l'allocation de mémoire. Donc, en supposant qu'il s'agit de la bibliothèque libjungle
, vous voulez probablement au moins jungle_setmalloc
et jungle_setfree
avec des valeurs par défaut sensibles. Vous pouvez ensuite configurer les global new
et delete
dans le code C++ de libjungle
pour utiliser ces fonctions définies par l'utilisateur.
C'est tout à fait possible. Voici comment, rapidement: 1.) Vous avez un header.h avec une API C qui n'inclut aucun Cplusiness.
#ifndef MIXEDCCPP_H
#define MIXEDCCPP_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h> // Any C-compatible headers will go here.
// C API goes here. C Functions can't contain any CPPiness.
void myclass_setName( void *pClassObj, const char *pName, int nameLen );
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
// Stuff that is only compatible with CPP goes here
// __cplusplus section won't get processed while compiling C files.
#include <vector> // CPP headers.
class MyClass {
// Classes etc.
};
#endif // #ifdef __cplusplus
#endif // MIXEDCCPP_H
Ensuite, dans le .cpp, vous créez simplement des fonctions C-API qui peuvent même inclure CPP directement:
#include "mixedccpp.h"
extern "C" {
// C API goes here. C Functions can't contain any CPPiness in their prototypes.
void myclass_setName( void *pClassObj, const char *pName, int nameLen )
{
// But CPP knowledge can go inside the function - no problem, since this is a CPP file.
MyClass *pMyClass = static_cast<MyClass *>(pClassObj);
pMyClass->setName( pName, nameLen );
}
} // #extern "C"
// CPP Stuff goes here... or vice-versa.
Dans votre cas, vous n'avez en fait besoin d'aucun code CPP déclaré dans votre en-tête puisque vous appelez des bibliothèques externes. Mais vous devez créer des fonctions compatibles C dans votre fichier CPP qui peuvent appeler les bibliothèques CPP. Utilisez extern "C" pour les fonctions qui doivent être appelées à partir de fichiers C, puis utilisez des structures C au lieu de classes et, si des classes sont nécessaires, utilisez void * pour les pointer vers elles, puis les restituer à leur classe à partir de la classe Fonction C à tout moment pour y accéder. Un makefile standard devrait être en mesure de compiler cela très bien, en supposant qu'il compile les fichiers .cpp en tant que .cpp et comprend extern "C" {}
Votre code C ne peut pas utiliser l'en-tête C++ <string>
. Vous devez vous assurer que les fonctions de l'API C++ qui doivent être appelées à partir de C sont déclarées extern "C"
(Comme vous l'avez) et n'utilisez que des types reconnus par un compilateur C (comme vous l'avez).
Vous devez également établir une liaison avec le compilateur C++ si l'un de vos codes est en C++. Vous pouvez le faire autrement si vous êtes prêt à dépenser beaucoup d'énergie pour obtenir les bonnes options du chargeur, mais il est beaucoup plus simple d'utiliser le compilateur C++:
gcc -c cmain.c
g++ -fPIC -shared -o cppfile.so cppfile.cpp
g++ -o cmain cmain.o cppfile.so
Bien sûr, vous devez:
#include <stdio.h>
Dans cmain.c
.std::string S(filename);
dans cppfile.cpp
.De plus, si le programme est invoqué sans arguments, vous obtenez:
$ ./cmain
terminate called throwing an exceptionAbort trap: 6
$ ./cmain x3
0
$
Vous devez vous protéger contre les abus, même dans les programmes de test.