Lately I spent a lot of time working on a small project of mine called libh2o. Its goal is to provide a library of routines implementing IAPWS IF97 equations for water and steam properties. With the core written in C, and providing a nice-to-use API for C++ and Python.
At first, I thought about not providing a «high level» C API at all. It was like: if you want to use plain C, you’ve gotta glue all the low-level equations yourself. However, after some thinking I decided to provide one, and built the two remaining APIs (C++ and Python) on top of it.
The main reason for doing this was that Python (well, CPython) is written in C. Although I’ve seen people writing Python extensions in C++, and even using some of C++ features to make them a little nicer, that’s still a bunch of ugly C hacks and pointer casts. I don’t see a really good reason to write a Python extension in C++, nor to make it depend on a C++ compiler when it’s all limited to C-based CPython API anyway.
And that means that I have either to duplicate all the high-level logic in the Python extension, or just create a C API first and reuse that. Since the whole logic was simple enough to be covered completely and clearly in C, I have chosen this way.
As it happens when people choose C, I had to implement some kind of poor man’s objectivity. Not something as wide (and ugly) as GObject (someone, please kill it!) but a few bits necessary to keep the state. In other words, a structure keeping the «object» and a bunch of nicely named functions taking it as their first argument.
Before I learnt C++, I would assume that the object structure should be a private (and obscured) blob, and the object type should be an incomplete pointer to it. User should just grab that pointer from a «constructor», pass it around and finally free it through a «destructor». Advantage: the exact struct contents are not the part of ABI.
But now I’ve decided to go the other way; way similar to how C++ classes work. I’ve created a structure with explicitly listed private fields (and a very simple
/*private:*/ comment), and used that as the public type. It doesn’t need to keep any memory allocated, and is simple enough to be allocated on stack. Advantages: no need for a destructor, and an ability to pack that struct in the C++ class which will wrap it.
Then the usual stuff: a bunch of functions with common prefixes. One prefix for the «namespace», another one for the function (
get…). All in nice and clear fashion, either to be used directly or wrapped in the C++ or Python APIs.