Writing C wrapper for C++ libraries
Writing C wrapper for C++ libraries
When working on projects that involve both C and C++ components, integrating functionality between the two can sometimes pose challenges due to the differences in their language constructs and compilation models. One common scenario is when you have a C++ library that you want to use within a C codebase.
Why Do this?
I encountered this issue when trying to use a serial communication library written in C++ in my C code.
Initially, I tried passing pointers of class methods to C functions, but this only works for static methods that do not reference any instance variables of the class.
Click here to see an example
Example:
class Car {
public:
static void dummyEngineOn()
{
std::cout << "Car::dummyEngineOn() called" << std::endl;
}
};
set_start_engine_fn(&Car::dummyEngineOn);
and C code as follows:
typedef void (*startEngine_fn)();
static startEngine_fn g_startEngine_fn;
void set_start_engine_fn(startEngine_fn fn) {
g_startEngine_fn = fn;
}
void start_engine() {
g_startEngine_fn();
}
I was not satisfied with this so looked to write a wrapper functions for C.
Basic Overview
So to write a wraper
We need to provide C-compatible functions as entry points that can be called from C code.
We need to use
extern "C"
to prevent C++ name mangling, allowing C functions to link properlyAddress linker issues if any.
Example Class
Let’s consider a simple example to illustrate the challenge. Suppose we have a C++ class called Car, which represents a car object with various methods and properties. Here’s a basic overview of the class:
// car.h (C++ header file)
class Car {
private:
int speed;
int fuel;
protected:
bool engineOn;
public:
Car();
void startEngine();
void stopEngine();
void accelerate(int amount);
void brake(int amount);
int getSpeed();
int getFuel();
bool isEngineOn();
};
Click here to see the implementation
// car.cpp (C++ source file)
#include "Car.h"
Car::Car() : speed(0), fuel(100), engineOn(false) {}
void Car::startEngine() {
engineOn = true;
}
void Car::stopEngine() {
engineOn = false;
}
void Car::accelerate(int amount) {
speed += amount;
}
void Car::brake(int amount) {
speed -= amount;
}
int Car::getSpeed() {
return speed;
}
int Car::getFuel() {
return fuel;
}
bool Car::isEngineOn() {
return engineOn;
}
To use this C++ class in C code, we will start by writing an interface where we decalre functions we want to expose to C code.
// CarWrapper.h (header file)
#ifndef CAR_WRAPPER_H
#define CAR_WRAPPER_H
#ifdef __cplusplus
extern "C" {
#endif
// Forward declaration of the Car struct
struct Car;
// Function declarations for the C interface
struct Car* create_car();
void destroy_car(struct Car* car);
void start_engine(struct Car* car);
void stop_engine(struct Car* car);
void accelerate(struct Car* car, int amount);
void brake(struct Car* car, int amount);
int get_speed(struct Car* car);
int get_fuel(struct Car* car);
int is_engine_on(struct Car* car);
#ifdef __cplusplus
}
#endif
#endif // CAR_WRAPPER_H
The
extern "C" {}
keyword is for the C++ comiler so that it does not add the extra mangling nformation to the symbol, hence it should be wrapped in#ifdef __cplusplus
.The struct name
Car
needs to be same as class nameCar
for ?? (I don’t know. Let me know as well. I have seen that with the same naming you do not have to provide the struct defination. I am not sure if the linker referes toclass Car
on seeingstruct Car
. I will not recommend using same name for both. See the section Different Struct name than Class name ).
The implementation of the C interface is in CarWrapper.cpp
is as follows:
// CarWrapper.cpp (C++ source file)
#include "CarWrapper.h"
#include "Car.h"
extern "C" {
struct Car* create_car() {
return new Car();
}
void destroy_car(struct Car* car) {
delete car;
}
void start_engine(struct Car* car) {
car->startEngine();
}
void stop_engine(struct Car* car) {
car->stopEngine();
}
void accelerate(struct Car* car, int amount) {
car->accelerate(amount);
}
void brake(struct Car* car, int amount) {
car->brake(amount);
}
int get_speed(struct Car* car) {
return car->getSpeed();
}
int get_fuel(struct Car* car) {
return car->getFuel();
}
int is_engine_on(struct Car* car) {
return car->isEngineOn() ? 1 : 0;
}
}
All set, now we write our main function
#include <stdio.h>
#include "CarWrapper.h"
int main(int argc, char** argv) {
struct Car* car = create_car();
start_engine(car);
accelerate(car, 40);
printf("Speed: %d\n", get_speed(car));
brake(car, 20);
printf("Speed: %d\n", get_speed(car));
stop_engine(car);
destroy_car(car);
return 0;
}
And compile it as
g++ -c car.cpp -o car.o
g++ -c CarWrapper.cpp -o CarWrapper.o
gcc main.c car.o CarWrapper.o -o main
And it fails!!! You encouter linker issues such as:
C:\msys64\ucrt64\bin\ld.exe: CarWrapper.o:CarWrapper.cpp:(.text+0x13): undefined reference to `operator new(unsigned long long)'
C:\msys64\ucrt64\bin\ld.exe: CarWrapper.o:CarWrapper.cpp:(.text+0x3d): undefined reference to `operator delete(void*, unsigned long long)'
C:\msys64\ucrt64\bin\ld.exe: CarWrapper.o:CarWrapper.cpp:(.text+0x73): undefined reference to `operator delete(void*, unsigned long long)'
C:\msys64\ucrt64\bin\ld.exe: CarWrapper.o:CarWrapper.cpp:(.xdata+0x10): undefined reference to `__gxx_personality_seh0'
collect2.exe: error: ld returned 1 exit status
Same issues in Unix Machine
/usr/bin/ld: car.o: in function `__static_initialization_and_destruction_0(int, int)':
car.cpp:(.text+0xec): undefined reference to `std::ios_base::Init::Init()'
/usr/bin/ld: car.cpp:(.text+0x107): undefined reference to `std::ios_base::Init::~Init()'
/usr/bin/ld: CarWrapper.o: in function `create_car':
CarWrapper.cpp:(.text+0x13): undefined reference to `operator new(unsigned long)'
/usr/bin/ld: CarWrapper.cpp:(.text+0x4f): undefined reference to `operator delete(void*, unsigned long)'
/usr/bin/ld: CarWrapper.o: in function `destroy_car':
CarWrapper.cpp:(.text+0x87): undefined reference to `operator delete(void*, unsigned long)'
/usr/bin/ld: CarWrapper.o: in function `__static_initialization_and_destruction_0(int, int)':
CarWrapper.cpp:(.text+0x190): undefined reference to `std::ios_base::Init::Init()'
/usr/bin/ld: CarWrapper.cpp:(.text+0x1ab): undefined reference to `std::ios_base::Init::~Init()'
/usr/bin/ld: CarWrapper.o:(.data.rel.local.DW.ref.__gxx_personality_v0[DW.ref.__gxx_personality_v0]+0x0): undefined reference to `__gxx_personality_v0'
collect2: error: ld returned 1 exit status
This is a linker issue because we do not have operator new
and operator delete
in the C library.
There are two options to resolve this:
Either compile using a C++ compiler:
g++ main.c car.o CarWrapper.o -o main
or Link the C++ standard library (
libstdc++
).
gcc main.c car.o CarWrapper.o -o main -lstdc++
Without Dynamic Allocation
If you do not want to use the dynamic allocation (which was true in my case), you can modify the wrapper to have a global static Car
object.
A global static object is allocated and initialized once, and its lifetime extends across the entire duration of the program. It gets destroyed when the program ends so you don’t have to worry about that. However, because the object is global and static, you can’t create multiple instances of it. (Well technically, nothing is stoppping you from statically allocating a fixed size array on n
objects.)
#include "CarWrapper.h"
#include "Car.h"
extern "C" {
struct Car g_car; // Static allocation of Car object
struct Car* create_car() {
return &g_car;
}
void destroy_car(struct Car* car) {
// No action needed for static allocation
}
// Other methods
}
Different Struct name than Class name
The struct name in the C wrapper does not need to be the same as the class name in C++. In fact I prefer to use a different name to avoid confusion and potential naming conflicts.
If you do not want to have the same nase as of the class name, you have to modify the struct such it correctly encapsulates the C++ class instance.
// CarWrapper.h (header file)
struct Vehicle;
// CarWrapper.cpp (C++ source file)
extern "C" {
struct Vehicle{
Car car;
} g_vehicle; // Static allocation of Vehicle object
struct Vehicle* create_car() {
return &g_vehicle; // new Vehicle(); in case of dynamic allocation
}
void destroy_car(struct Vehicle* car) {
// No action needed for static allocation
// delete car; in case of dynamic allocation
}
void start_engine(struct Vehicle* car) {
car->car.startEngine();
}
void stop_engine(struct Vehicle* car) {
car->car.stopEngine();
}
// Other methods
}
Conclusion
By creating a C wrapper for a C++ library, you can integrate C++ functionality into a C codebase. I wrote a C wrapper for C++ cross-platform RS232 serial communication library by Fifi Lulu
Comments