Question

So I've read Developing C wrapper API for Object-Oriented C++ code and I like the approach, which I have taken with my library - opaque handles for each corresponding C++ class; avoiding using void*

But now, I'm faced with thinking about 'interfaces', and base classes. For example, I have a class hierarchy of "channel" classes - a base class for a "channel" and derived concrete classes for, for example, serial comms, in-memory buffers, sockets, etc.

So I have:

typedef struct serial_channel serial_channel;
typedef struct socket_channel socket_channel;
typedef struct memory_channel memory_channel;

serial_channel* create_serial_channel();
socket_channel* create_socket_channel();
memory_channel* create_memory_channel();

But I want to be able to pass any one of those into a function to associate it with a 'device' object:

void associate_device_with_channel(device*, channel*);

Easy in C++, since it understands base classes. How do I approach this in the C wrapper library - what type is channel in C?

The only thing I can think of is that I must resort to void* to represent a base class?

typedef void* channel;
void associate_device_with_channel(device*, channel*);

It works, but would let me pass any pointer?

On the other extreme, I can write a set of functions matching the derived channel classes:

void associate_device_with_serial_channel(device*, serial_channel*);
void associate_device_with_socket_channel(device*, socket_channel*);
void associate_device_with_memory_channel(device*, memory_channel*);

It's very verbose, and if I have to add new channel types, I have to add new functions to the interface as well.

Is there some kind of middle ground I've been missing? - like a single function, but not void*?

Was it helpful?

Solution

There isn't any perfect approach. You're trying to make your function take some opaque handles (the ones with the appropriate base class) but not any handle type (which void* would accept), and there just isn't a thing in C for that.

If you like, you can provide a function which takes serial_channel* and returns channel*, and another one for each other channel subclass. This gets you away from unsafe C casting, and doesn't require numfuncs*numderivedclasses different channel-taking functions.

Personally, I'd just void* it. They're using C, after all... clearly they don't care too much about their language keeping them safe.

OTHER TIPS

First, I would set up my structures something like this:

typedef void base_class;
struct base_class_impl
{
    // base class member variables go here
}
struct derived_class
{
    // base class must come first in the derived struct
    struct base_class_impl base;
    // derived class member variables go here
}

Then, I would take pointers to base_class as arguments to my functions:

int base_class_get_count(base_class *b);

and I would always cast at the start of the function:

int base_class_get_count(base_class *b)
{
    struct base_class *base = (struct base_class *)b;
    // Operate on the object now
}

This enables base_class_get_count() to work even on objects of the derived type. The downside is that it doesn't allow the derived type to override a method - you would have to go a step further, implementing your own table of function pointers which the API calls (like base_class_get_count) dispatch out to, based on the entry in the table.

If you only target GCC or Clang (I suspect that you wouldn't bother with C if you were targeting Visual Studio), one of your options is to create a union with the non-standard __transparent_union__ attribute to list the types that a function can accept. A function accepting a union parameter with the __transparent_union__ attribute will accept either that union, or any type contained in it.

union associable_channel
{
    channel* a;
    serial_channel* b;
    socket_channel* c;
    memory_channel* d; 
} __attribute__((__transparent_union__));

void associate_device_with_channel(union associable_channel chan);

serial_channel* serial;
socket_channel* socket;
memory_channel* mem;
associate_device_with_channel(serial);
associate_device_with_channel(socket);
associate_device_with_channel(mem);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top