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.