Элегантно вызывайте C ++ из C - PullRequest
47 голосов
/ 02 сентября 2011

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

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

Ответы [ 4 ]

55 голосов
/ 02 сентября 2011

РЕДАКТИРОВАТЬ: Основываясь на обсуждении в комментарии, я должен отметить, что разделение вещей на C-совместимый struct duck и производное class Duck, вероятно, не требуется.Вероятно, вы можете безопасно переложить реализацию в struct duck и исключить class Duck, тем самым исключив real(…).Но я не знаю C ++ достаточно хорошо (в частности, как он взаимодействует со вселенной C), чтобы дать однозначный ответ на этот вопрос.


Нет причины, по которой вы не можете просто связать всеваш код на C и C ++ объединен в один двоичный файл.

Для взаимодействия с кодом C ++ необходимо обернуть C ++ API в C API.Вы можете сделать это, объявив несколько функций внутри extern "C" { ... } при компиляции кода 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

Вы можете определить структуру утки в вашем источнике C ++ и даже наследовать от нее настоящий класс 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); }
7 голосов
/ 06 июня 2013

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

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

Но, может быть, это констатирует очевидное ....

7 голосов
/ 06 июня 2013

Единственная причина хотеть унаследовать от структуры утки - это выставить некоторые атрибуты в 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); }

Таким же образом,C API может быть создан для интерфейса C ++ (чисто виртуальный класс) и его реализаций.В этом случае только конструктор должен быть основан на конкретной реализации (например, new_RubberDuck (2)).Деструктор и все другие функции будут автоматически работать в правильной реализации, как в C ++.

1 голос
/ 09 сентября 2015

Там - это способ создать «хак», который позволяет напрямую вызывать функции-члены некоторых объектов.

Первое, что вам нужно сделать, это создать фабричную функцию extern "C", которая возвращает указатель (как void*) на объект.

Второе, что вам нужно, это искаженное имя функции-члена.

Затем вы можете вызвать функцию, используя искаженное имя и передав указатель, возвращенный из фабричной функции, в качестве первого аргумента.

Предостережения:

  • Конечно, не будет работать вызов функции-члена, которая хочет другие объекты или ссылки, или другие вещи C ++, или функции, возвращающие объекты или типы, несовместимые с типами C
  • Не будет работать с виртуальными функциями-членами и, вероятно, не с объектами, в которых есть виртуальные функции, даже если это не виртуальная функция, вызываемая
  • Искаженное имя должно быть действительным символом C
  • Много-много-много ...

Это не то, что я рекомендую, скорее наоборот. Я настоятельно рекомендую не делать что-то, как описано в этом ответе. Это неподдерживаемое и, вероятно, неопределенное поведение, которое может привести к странным и непредсказуемым последствиям.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...