Элегантно вызывать С++ из C
Мы разрабатываем некоторый проект в простой C
(C99). Но у нас есть одна библиотека в качестве исходных кодов (математическая библиотека) в C++
. Нам нужна эта библиотека, поэтому я хотел бы спросить, что является самым элегантным способом интеграции этих исходных кодов?
Отношение между размерами C
и C++
равно 20:1
, поэтому переход на C++
не является опцией. Должны ли мы использовать статическую библиотеку? DLL? (Все это в Windows).
Ответы
Ответ 1
EDIT: Основываясь на обсуждении в комментарии, я должен указать, что разделение вещей на C-совместимое struct duck
и производное class Duck
, вероятно, не нужно. Вероятно, вы можете безопасно перевести реализацию в struct duck
и устранить class Duck
, тем самым избавившись от real(…)
. Но я не знаю С++ достаточно хорошо (в частности, как он взаимодействует с вселенной C), чтобы дать окончательный ответ на это.
Нет причин, по которым вы не можете просто связать все ваши C и С++-коды вместе в один бинарный файл.
Взаимодействие с кодом С++ требует, чтобы вы завернули API С++ в API C. Вы можете сделать это, объявив кучу функций внутри extern "C" { ... }
при компиляции кода С++ и без объявления extern при компиляции кода клиента C. Например:.
#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
Вы можете определить структуру утки в источнике С++ и даже наследовать от нее реальный класс 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); }
Ответ 2
Единственная причина, по которой нужно унаследовать от duck struct, - это показать некоторые атрибуты в API C, которые в любом случае считаются плохими. Без наследования ваш заголовок C будет выглядеть так:
struct Duck;
struct Duck* new_Duck(int feet);
void delete_Duck(struct Duck* d);
void Duck_quack(struct Duck* d, float volume);
И это будет соответствующая реализация, без необходимости приведения типов:
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); }
Таким же образом API C может быть создан для интерфейса С++ (чистый виртуальный класс) и его реализации. В этом случае только конструктор должен основываться на конкретной реализации (например, new_RubberDuck (2)). Деструктор и все другие функции будут автоматически работать с правильной реализацией, как и на С++.
Ответ 3
Математическая библиотека С++ вполне может быть реализована в классах служебных программ (только для статических членов). В этом случае можно было бы сделать гораздо более простой подход:
class FPMath {
public:
static double add(double, double);
static double sub(double, double);
static double mul(double, double);
static double div(double, double);
};
Заголовок для интерфейса C будет следующим:
double FPMath_add(double, double);
double FPMath_sub(double, double);
double FPMath_mul(double, double);
double FPMath_div(double, double);
И соответствующая реализация может быть:
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); }
Но, возможно, это говорит о явном....
Ответ 4
Существует способ создания "взлома", который позволяет вам напрямую вызвать функции-члены некоторых объектов.
Первое, что вам нужно сделать, это создать функцию extern "C"
factory, которая возвращает указатель (как void*
) к объекту.
Вторая вещь, в которой вы нуждаетесь, - это искаженное имя функции-члена.
Затем вы можете вызвать функцию, используя исковое имя, и передать указатель, возвращенный из функции factory, в качестве первого аргумента.
Предостережения:
- Конечно, не будет работать функция-член вызова, которая хочет другие объекты или ссылки или другие объекты С++, или функции, возвращающие объекты или типы, не совместимые с типами C.
- Не будет работать с виртуальными функциями-членами и, возможно, не на объектах с виртуальными функциями в них, даже если это не виртуальная функция, называемая
- Нарушенное имя должно быть допустимым символом C
- Любые многие другие...
Это не то, что я рекомендую, совсем наоборот. Я настоятельно рекомендую не делать что-то вроде изложенного в этом ответе. Это неподдерживаемое и, возможно, поведение undefined и может нарушаться странными и непредсказуемыми способами.