Wrapping C++ objects in C

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.

  1. Make a static library.
  2. 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