Introduction
Using C functions from C++ is very easy but going the other way isn’t. However, it can be done with a little ingenuity. Really, this isn’t as crazy as it sounds.
I’ll use a simple adder for the object. It takes an integer as a starting value and has two functions for adding and getting the current value. This could be expanded later into a complex object for mathematical operations but that’s really not necessary right now.
The C++ Object We Want to Wrap
cppmather.h
#ifndef __CPPMATHER_H__
#define __CPPMATHER_H__
class CPPMather
{
public:
CPPMather(int start);
void add(int val);
int val();
private:
int value;
};
#endif // __CPPMATHER_H__
cppmather.cpp
#include "cppmather.h"
CPPMather::CPPMather(int start)
{
value = start;
}
void CPPMather::add(int val)
{
value += val;
}
int CPPMather::val()
{
return value;
}
The Wrapper
The wrapper is C++ but exposes itself as C. Meaning the exposed C functions are C functions. However, this needs to be compiled as C++ in order to use new and delete for proper C++ class initialization.
mather.h
#ifndef __MATHER_H__
#define __MATHER_H__
#ifdef __cplusplus
extern "C" {
#endif
struct mather;
typedef struct mather mather_t;
mather_t *mather_create(int start);
void mather_destroy(mather_t *m);
void mather_add(mather_t *m, int val);
int mather_val(mather_t *m);
#ifdef __cplusplus
}
#endif
#endif /* __MATHER_H__ */
mather.cpp
#include <stdlib.h>
#include "mather.h"
#include "cppmather.h"
struct mather {
void *obj;
};
mather_t *mather_create(int start)
{
mather_t *m;
CPPMather *obj;
m = (typeof(m))malloc(sizeof(*m));
obj = new CPPMather(start);
m->obj = obj;
return m;
}
void mather_destroy(mather_t *m)
{
if (m == NULL)
return;
delete static_cast<CPPMather *>(m->obj);
free(m);
}
void mather_add(mather_t *m, int val)
{
CPPMather *obj;
if (m == NULL)
return;
obj = static_cast<CPPMather *>(m->obj);
obj->add(val);
}
int mather_val(mather_t *m)
{
CPPMather *obj;
if (m == NULL)
return 0;
obj = static_cast<CPPMather *>(m->obj);
return obj->val();
}
The wrapper creates a struct
and puts the CPPMather
object into it as as
void pointer. This struct
allows us to use it in a type safe manner in C. A
void pointer could be used instead of the struct
but it’s not obvious what
the object represents. We want to avoid situations where the wrong object is
passed to the wrong function(s). If you absolutely do not want to wrap the
object in a struct
you could define a type and use that instead. Truthfully,
we should never expose a void pointer directly. You want to keep type safety
and wrap any casting in the implementation.
obj = static_cast<CPPMather *>(m->obj);
static_cast
is used to convert the stored void pointer into CPPMather
, and
pretty much every function is going to follow this pattern. A C style cast can
be used but it’s better to avoid it in C++ code because the compiler can give
better indications of misuse.
Example application
The C file (main.c) will use the C functions exposed by the wrapper’s header file.
main.c
#include <stdio.h>
#include "mather.h"
int main(int argc, char **argv)
{
mather_t *m = mather_create(4);
mather_add(m, 6);
printf("%d\n", mather_val(m));
mather_destroy(m);
return 0;
}
Build and Running
In this example the C++ code will be compiled with a C++ compiler into a
library, either shared or static. If compiling as static the application (not
the static library) will additionally need to be linked to -lstdc++
. The C
code (application) will be compiled with a C compiler. As far as the
application is concerned it is calling a library and it doesn’t matter what
language the library is written in. All that matters is library (due to the
extern C) exposes the symbols it needs to link to.
Shared
$ g++ -shared -o libmather.so mather.cpp cppmather.cpp -lc
$ gcc -o bridge main.c -lmather -L.
Don’t forget that the shared library need to be in the search path for the application.
Static
There are a few ways to handle static linking.
- Make a static library.
- Link the .o files directly into the C application.
Library
$ g++ -c cppmather.cpp mather.cpp
$ ar rcs libmather.a mather.o cppmather.o
$ gcc -o bridge main.c libmather.a -lstdc++
The .cpp files are first compiled into .o file. Then, ar
collects the .o
files and creates a single archive that is the static library.
Direct Linking
$ g++ -c cppmather.cpp mather.cpp
$ gcc -o bridge main.c mather.o cppmather.o -lstdc++
Here the .o files are referenced directly instead of having been first collected into a single .a file.
CMake
The application can also be built using CMake. The beauty of this approach is CMake uses the file extension to determine how to compile and handles the necessary linking. There is no need to create an intermediate shared or static library.
cmake_minimum_required (VERSION 3.0)
project (bridge)
# Put binary in a bin dir.
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
set (SOURCES
main.c
cppmather.cpp
mather.cpp
)
add_executable (${PROJECT_NAME} ${SOURCES})
Since this uses mixed languages the language type is omitted from the project directive.
[ 25%] Building C object CMakeFiles/bridge.dir/main.c.o
.../cc -o main.c.o -c main.c
[ 50%] Building CXX object CMakeFiles/bridge.dir/cppmather.cpp.o
.../c++ -o cppmather.cpp.o -c cppmather.cpp
Using make VERBOSE=1
we can verify (abbreviated output above) that the C and
C++ compilers are used on the correct C and C++ source files.
Output
$ ./bridge
10