Элегантно вызывать С++ из C - программирование
Подтвердить что ты не робот

Элегантно вызывать С++ из C

Мы разрабатываем некоторый проект в простой C (C99). Но у нас есть одна библиотека в качестве исходных кодов (математическая библиотека) в C++. Нам нужна эта библиотека, поэтому я хотел бы спросить, что является самым элегантным способом интеграции этих исходных кодов?

Отношение между размерами C и C++ равно 20:1, поэтому переход на C++ не является опцией. Должны ли мы использовать статическую библиотеку? DLL? (Все это в Windows).

4b9b3361

Ответ 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 и может нарушаться странными и непредсказуемыми способами.