Elegantly call C++ from C

40,802

Solution 1

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

Solution 2

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++.

Solution 3

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....

Solution 4

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.

Share:
40,802
Cartesius00
Author by

Cartesius00

Fun

Updated on July 09, 2022

Comments

  • Cartesius00
    Cartesius00 almost 2 years

    We develop some project in plain C (C99). But, we have one library as source codes (math library) in C++. We need this library so I would like to ask, what is the most elegant way to integrate this source codes?

    Ratio between sizes of C and C++ is 20:1 so moving to C++ is not the option. Should we use static library? DLL? (It's all on Windows).

  • Alex S
    Alex S over 12 years
    @Ulterior: Not IMO. If you represent ducks and frogs as void pointers, you might accidentally get a frog to quack, which would tear apart the fabric of the universe.
  • Raffi Khatchadourian
    Raffi Khatchadourian over 11 years
    If you want to wrap an STL class, for example, you won't be able to derive from a struct like duck above. In that case, would void pointers be the only route?
  • Alex S
    Alex S over 11 years
    @RaffiKhatchadourian: It should never be required. In the case of an STL container, you could forgo the inheritance and hold the container directly in the struct: struct ducks { std::vector<Duck> d; };. My Duck example could have been the same, albeit with the inconvenience of duck needing a forwarding constructor.
  • Raffi Khatchadourian
    Raffi Khatchadourian over 11 years
    How do you go about compiling this? Do you need to compile your C source code with g++ (instead of gcc) to get this working?
  • Alex S
    Alex S over 11 years
    @RaffiKhatchadourian: The source for Duck is C++ code, and therefore must be compiled with a C++ compiler. Code that includes the header file in order to make use of the Duck class can be C or C++ code, and compiled as such.
  • mtvec
    mtvec almost 11 years
    Is there any reason this method should be preferred over Arnd Strube's answer? That is, is there any danger in using pointers to class Duck instead of to struct duck?
  • Alex S
    Alex S over 10 years
    @Job: Amd Strube's answer produces numerous warnings at any reasonable warning level. Changing class Duck to struct Duck fixes this, and is (I think) a reasonable alternative to my solution. I prefer to keep the C and C++ universes as cleanly separated as possible by defining struct duck as a C-compatible type.
  • uliwitness
    uliwitness about 10 years
    Why do you think the real() function is needed? struct and class are exchangeable in C++ (the only difference is that if you use struct when defining a class in C++, all members are public by default). So really, the inheritance and real() function aren't needed. Forward-declare struct duck in the header and use only pointers and C will be happy. Include the regular C++ duck header in the implementation and add the requisite extern "C" calls and C++ does the right thing.
  • uliwitness
    uliwitness about 10 years
    The function implementations should also be labeled as extern "C". Otherwise you get link errors because "new_Duck::i" or whatever name mangling on your platform makes of C++ functions doesn't match the header's "new_Duck".
  • Alex S
    Alex S about 10 years
    @uliwitness: From my previous comment: "Changing class Duck to struct Duck … is (I think) a reasonable alternative to my solution." I then go on to explain why I prefer my approach.
  • uliwitness
    uliwitness about 10 years
    @Marcelo Cantos that explanation is what I'm asking about. How is subclassing (a C++ operation) separating the C and C++ universes?
  • Alex S
    Alex S about 10 years
    @uliwitness: Fine, but that's not the same as me saying the real() function is needed. To clarify my position, a struct with methods isn't a C struct. A struct with virtual methods, virtual base classes, private and protected sections, etc., is even less a C struct. I want the pointer that I hand to the caller to point to a bona fide 100% plain old C struct with no C++ bits attached; the fact that it's a sub-object of the derived class doesn't change that. OCD? Perhaps, but separating concepts, even if there's no obvious need, keeps things simple in my head.
  • uliwitness
    uliwitness about 10 years
    @MarceloCantos Just pointing out that, the way C++ is defined, there is no guarantee that you get from this that you don't get from just forward-declaring the class as a struct in the header. As long as you only expose the declaration. If you expose the definition, it won't work in C either. Keeping the code simpler and reducing the number of lines that could contain bugs in this case is preferable, IMO.
  • Alex S
    Alex S about 10 years
    @uliwitness: You're probably right, but then I wonder what would happen if I added a virtual base class to the struct, or multiple inheritance. I think it would be just fine, and I have you assuring me that it is, but, much as I love using C++, it isn't my only language, and I don't have the time to investigate it sufficiently to convince myself that it's safe. I also believe you when you say it's safe, but there's still a nugget of doubt (has uliwitness considered all the edge cases? What if the need to cast through void * arises somewhere?).
  • Alex S
    Alex S about 10 years
    So I fall back on a strategy that I can be 100% certain of without further research. One day, I'll probably get off my lazy arse, go do some homework and realise, "Hey, uliwitness was right on the money!" Maybe you can short-circuit the process and point to an authoritative source that confirms this. For now, I'll edit my answer to underscore my ignorance on the topic.
  • schanti schul
    schanti schul almost 3 years
    Shortly, how does it work, how does the .c file that includes it sees it, and how a different .cpp file that includes it sees it?