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*?

有帮助吗?

解决方案

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.

其他提示

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);
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top