EDIT: Based on discussion in the comment, I should point out that separating things into a C-compatible struct duck and a derived class Duck is probably unnecessary. You can probably safely shovel the implementation into struct duck and eliminate class Duck, thus obviating real(…). But I don't know C++ well enough (in particular, the way it interacts with the C universe) to offer a definitive answer on this.


There is no reason you can't simply link all your C and C++ code together into a single binary.

Interfacing to the C++ code requires that you wrap the C++ API in a C API. You can do this by declaring a bunch of functions inside extern "C" { ... } when compiling the C++ code, and without the extern declaration when compiling the C client code. E.g.:

#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

You can define the duck struct in your C++ source, and even inherit the real Duck class from it:

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

The only reason to want to inherit from the duck struct would be to expose some to its attributes in the C API, which is generally considered bad style anyway. Without inheritance, your C header would look like this:

struct Duck;

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

And this would be the corresponding implementation, with no need for type casts:

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

In the same way, a C API can be created for a C++ interface (pure virtual class) and its implementations. In that case, only the constructor need to be based on the concrete implementation (e.g. new_RubberDuck(2)). The destructor and all other functions will automatically operate on the correct implementation, same as in C++.


A C++ math library may well be implemented in the for of utility classes (static members only). In this case, a much simpler approach could be taken:

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

The header for the C interface would then be:

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

And the corresponding implementation might be:

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

But maybe this is stating the obvious....


There is a way to create a "hack" that allows you to call member functions of some objects directly.

The first thing you have to do is to create an extern "C" factory function, which returns a pointer (as void*) to the object.

The second thing you need is the mangled name of the member function.

Then you can call the function using the mangled name, and passing the pointer returned from the factory function as the first argument.

Caveats:

  • Will of course not work calling member function that wants other objects, or references, or other C++ stuff, or functions returning objects or types not compatible with C types
  • Will not work on virtual member functions, and probably not on objects with virtual functions in them even if it's not a virtual function being called
  • The mangled name have to be a valid C symbol
  • Any many many more...

This is not something I recommend, quite the opposite in fact. I strongly advise against doing something like outlined in this answer. It's unsupported and probably undefined behavior and may break in weird and unpredictable ways.