web-dev-qa-db-fra.com

Appelez élégamment C ++ à partir de C

Nous développons un projet en plain C (C99). Mais, nous avons une bibliothèque en tant que codes source (bibliothèque mathématique) dans C++. Nous avons besoin de cette bibliothèque, donc je voudrais demander, quelle est la façon la plus élégante d'intégrer ces codes sources?

Rapport entre les tailles de C et C++ est 20:1 donc passer à C++ n'est pas l'option. Faut-il utiliser une bibliothèque statique? DLL? (Tout est sous Windows).

47
Cartesius00

EDIT: Sur la base de la discussion dans le commentaire, je dois souligner que la séparation des choses en un struct duck Compatible C et un dérivé class Duck N'est probablement pas nécessaire. Vous pouvez probablement pelleter l'implémentation en toute sécurité dans struct duck Et éliminer class Duck, Évitant ainsi real(…). Mais je ne connais pas assez bien le C++ (en particulier, la façon dont il interagit avec l'univers C) pour offrir une réponse définitive à ce sujet.


Il n'y a aucune raison pour laquelle vous ne pouvez pas simplement lier tous vos codes C et C++ en un seul binaire.

L'interfaçage avec le code C++ nécessite que vous encapsuliez l'API C++ dans une API C. Vous pouvez le faire en déclarant un tas de fonctions à l'intérieur de extern "C" { ... } Lors de la compilation du code C++, et sans la déclaration externe lors de la compilation du code client C. Par exemple.:

#ifdef __cplusplus
extern "C" {
#endif

typedef struct duck duck;

duck* new_duck(int feet);
void delete_duck(duck* d);
void duck_quack(duck* d, float volume);

#ifdef __cplusplus
}
#endif

Vous pouvez définir la structure duck dans votre source C++, et même en hériter la vraie classe Duck:

struct duck { };

class Duck : public duck {
public:
    Duck(int feet);
    ~Duck();

    void quack(float volume);
};

inline Duck* real(duck* d) { return static_cast<Duck*>(d); }

duck* new_duck(int feet) { return new Duck(feet); }
void delete_duck(duck* d) { delete real(d); }
void duck_quack(duck* d, float volume) { real(d)->quack(volume); }
55
Marcelo Cantos

La seule raison de vouloir hériter de la structure duck serait d'exposer certains à ses attributs dans l'API C, qui est généralement considérée comme un mauvais style de toute façon. Sans héritage, votre en-tête C ressemblerait à ceci:

struct Duck;

struct Duck* new_Duck(int feet);
void delete_Duck(struct Duck* d);
void Duck_quack(struct Duck* d, float volume);

Et ce serait l'implémentation correspondante, sans avoir besoin de transtypages de type:

extern "C" {
#include "Duck.h"
}

class Duck {
public:
    Duck(int feet) : {}
    ~Duck() {}

    void quack(float volume) {}
};

struct Duck* new_Duck(int feet) { return new Duck(feet); }
void delete_Duck(struct Duck* d) { delete d; }
void Duck_quack(struct Duck* d, float volume) { d->quack(volume); }

De la même manière, une API C peut être créée pour une interface C++ (classe virtuelle pure) et ses implémentations. Dans ce cas, seul le constructeur doit être basé sur l'implémentation concrète (par exemple new_RubberDuck (2)). Le destructeur et toutes les autres fonctions fonctionneront automatiquement sur l'implémentation correcte, comme en C++.

7
A.Robert

Une bibliothèque mathématique C++ peut très bien être implémentée dans le for des classes utilitaires (membres statiques uniquement). Dans ce cas, une approche beaucoup plus simple pourrait être adoptée:

class FPMath {
public:
    static double add(double, double);
    static double sub(double, double);
    static double mul(double, double);
    static double div(double, double);
};

L'en-tête de l'interface C serait alors:

double FPMath_add(double, double);
double FPMath_sub(double, double);
double FPMath_mul(double, double);
double FPMath_div(double, double);

Et l'implémentation correspondante pourrait être:

double FPMath_add(double a, double b) { return FPMath::add(a, b); }
double FPMath_sub(double a, double b) { return FPMath::sub(a, b); }
double FPMath_mul(double a, double b) { return FPMath::mul(a, b); }
double FPMath_div(double a, double b) { return FPMath::div(a, b); }

Mais cela indique peut-être l'évidence ...

7
A. Robert

Il y a is un moyen de créer un "hack" qui vous permet d'appeler directement les fonctions membres de certains objets.

La première chose à faire est de créer un extern "C" fonction d'usine, qui renvoie un pointeur (comme void*) à l'objet.

La deuxième chose dont vous avez besoin est le nom modifié de la fonction membre.

Ensuite, vous pouvez appeler la fonction en utilisant le nom modifié et en passant le pointeur renvoyé par la fonction d'usine comme premier argument.

Mises en garde:

  • Bien sûr, ne fonctionnera pas en appelant une fonction membre qui souhaite d'autres objets, ou des références, ou d'autres choses C++, ou des fonctions renvoyant des objets ou des types non compatibles avec les types C
  • Ne fonctionnera pas sur les fonctions membres virtuelles, et probablement pas sur les objets contenant des fonctions virtuelles même si ce n'est pas une fonction virtuelle appelée
  • Le nom mutilé doit être un symbole C valide
  • Beaucoup plus encore ...

Ce n'est pas quelque chose que je recommande, bien au contraire en fait. Je déconseille fortement de faire quelque chose comme décrit dans cette réponse. C'est un comportement non pris en charge et probablement indéfini et peut se briser de manière étrange et imprévisible.

1