0

Are there any available guidelines, habits, best practices... on how to expose a C++ library via C bindings (compiled as part of the library).

The objective is to not modify the C++ classes.

Assuming the following two C++ classes

class B {
   public:
    std::vector<int> list();
    std::string toString();
};

class A {
   public:
    explicit A(size_t size);
    B getB();
};

What would be the "best" way to expose these as a C API; a list of functions handling pointers and instantiating objects on the heap, callbacks ? Is there a list of generally accepted naming conventions ?

extern "C" {

    A* A_new(size_t size);
    void A_delete(A* a);

    B* A_getB(A *a);

    void B_delete(B* b);
    ...
}

(note: asking for generally accepted practices and conventions and/or tools is not asking for opinions but practical feedback)

Bruno Grieder
  • 28,128
  • 8
  • 69
  • 101
  • 1
    http://www.swig.org/ – Alan Birtles Feb 14 '19 at 16:34
  • Come on, not SWIG... Anything but SWIG. – Matthieu Brucher Feb 14 '19 at 16:41
  • Possible duplicate https://stackoverflow.com/questions/31903005/how-to-mix-c-and-c-correctly – Galik Feb 14 '19 at 16:50
  • @Galik thanks, that is interesting and I did not find it while browsing, though this SO post is more about **mixing** C and C++ – Bruno Grieder Feb 14 '19 at 16:52
  • 1
    Note that "generally accepted practices and conventions" is an opinion-based matter, and asking for tools is also off-topic here. – TylerH Feb 14 '19 at 21:35
  • @TylerH errr... A convention is something people use to precisely avoid loosing time on opinions. The keywords of the C++ language are just conventions, 1+1=2 too and the English words you used to type your comment are as well. IMHO the "purist" league on SO should cool it down. I have been programming for over 30 years, in many languages and I know respecting conventions are as important as good engineering. Talk to your UX designer for details. Thanks. – Bruno Grieder Feb 15 '19 at 07:51
  • @BrunoGrieder the SO community acknowledges that conventions are simply *opinions* agreed upon by people and thus are off-topic; insulting them will not do you any favors. Likewise ageism will get you nowhere; old people can still be wrong. If you want advice on design decisions or opinion-based subjects like this, SO is the wrong site. There *are*, however, some sites that may be appropriate, such as https://softwareengineering.stackexchange.com/ or if you have a question that falls within their [scope](https://codereview.stackexchange.com/help/on-topic), https://codereview.stackexchange.com/ – TylerH Feb 15 '19 at 14:45
  • Relax, *you* are not the SO community, nor their elected rep. Instead of wasting time criticizing questions, I suggest you spend more time on positive things, like helping. Again, relax, and have a nice day. – Bruno Grieder Feb 15 '19 at 16:33

3 Answers3

2

My experience is that the basic question to answer is whether the C++ objects are singletons of some kind or if there will be multiple instances of these objects on the C side of the interface.

If they are singletons then the C interface can be a set of functions and all of the management, object handles, and other bits are held within the C++ side of the interface. The user of the library doesn't need to have any kind of a way to reference a particular C++ object.

However if there are multiple instances of the C++ objects then the decision as to how to handle these multiple objects and their lifetime has to be determined. The main two ways I have seen are (1) providing a pointer to the C++ object over to the C side of the interface or (2) providing a handle to the C++ object over to the C side of the interface with the handle referencing a management area on the C++ side.

I personally like the handle method because then modern C++ practices can be used for managing the lifetime of the objects. The objects are stored in some C++ container with a unique handle or identifier provided to the C interface and the C++ side owns the objects. The C side is requesting specific operations on the objects through the C interface.

The C interface may vary between being unique functions that map to the individual C++ object methods or being a more generic function that takes what amounts to a command flag by specifying a unique identifier for the C++ object method.

In the first alternative the methods of the C++ object are exposed as a C interface function which, in the case of multiple instances, takes either a pointer to the C++ object or the handle to the object. The C interface function, written in C++, then does the actual C++ object method call with the proper arguments. So this is just a C interface function that forwards the parameters to the C++ object.

In the second alternative, the C++ interface function has to determine, using a lookup table or a switch or something similar which method to invoke. Parameters may either be specified in the interface or variable arguments may be used. If variable arguments are used then you have the problem of parsing out the parameter list, marshaling the arguments, and then calling the C++ object method.

An additional consideration is the naming and namespace clutter. In C there is the traditional three letter prefix approach to naming of externals though in most cases more characters are more meaningful.

However the approach I really prefer is to have an external struct within which are function pointers to the C functions making up the interface. By doing this, you can name the struct something meaningful for your library and then you are free to name the actual function pointers however you like.

The actual C interface functions are static within the source file which exposes only the struct containing the function pointers. The struct itself is a global that is initialized when it is defined at the bottom of the source file containing the C interface functions.

Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
  • Interesting. I like the struct with function pointers. Regarding the container for object handles: is this something you handcraft yourself or you start from an existing container ? How do you handle eviction of the objects from the container ? The caller has to specifically request eviction/deletion ? – Bruno Grieder Feb 14 '19 at 17:40
  • @BrunoGrieder I prefer using standard containers as much as possible. I'm not sure what you mean by "eviction of the objects". The life time management really depends on how the objects are being used and how long they are staying around. Most of what I have dealt with are vectors of objects that are created with long lifetimes. The container owns the objects and the caller is requesting particular operations to be performed. – Richard Chambers Feb 14 '19 at 17:47
1

What you're proposing is typically the best way to handle this. You have a function which return a pointer to a newly allocated object, a set of function that take that pointer and do various things, and one more that takes the pointer and frees the memory.

The only thing you need to add is a declaration for the type that is compatible with C:

typedef struct A A;
typedef struct A B;
dbush
  • 205,898
  • 23
  • 218
  • 273
1

You shouldn't expose std types in your C interface (public members in class B) because the C++ ABI is not standardized and you might end up with bugs that are very hard to figure out when the API consumer is using a different compiler version.

For example in your code I would rewrite B as follows:

class B {
   public:
    int* list(); // or write your own implementation for a vector type which you ship with the api
    const char* toString();
};
Claudiu Guiman
  • 837
  • 5
  • 18
  • Yes, I realize that. For vectors fortunately, that is easy with the .data() method. – Bruno Grieder Feb 14 '19 at 17:06
  • That's a very important point, also for the other way around (C libraries with a C++ wrapper should not use `std::string`, but hdf5 breaks that ... – Walter Feb 14 '19 at 19:57