I'm developing a c-api wrapper for a c++ library. I'm also the maintainer of the c++ library.
My basic strategy, which is working so far, is to have an opaque handle for every c++ class in the library. I'm declaring the handles as structs:
struct Handle_;
typedef struct Handle_ *Handle;
Then in my c-api wrapper header I have a function for each member function of the class, including creation and destruction. I've implemented it like described in this post: https://stackoverflow.com/a/2045860/3237961
But know I have the following problem:
Note: To reproduce the problem no c-api wrapper is needed. It's all about casting.
Assuming I have a class which inherit from two abstract class (i.e. interfaces):
class T_IDeviceInfo {
public:
virtual int getDeviceType() = 0;
};
class T_IDeviceControl {
public:
virtual void open() = 0;
};
class T_Device : public T_IDeviceInfo, public T_IDeviceControl {
public:
int getDeviceType() override {
std::cout << "T_Device::getDeviceType()" << std::endl;
return 0;
}
void open() override {
std::cout << "T_Device::open()" << std::endl;
}
};
In the following code I'm using the above declared class:
int main(){
//Prints the device type message and the open message from T_Device
T_Device* device = new T_Device();
device->getDeviceType();
device->open();
//works
std::cout << "cast as derived class" << std::endl;
reinterpret_cast<T_IDeviceInfo*>(device)->getDeviceType();
reinterpret_cast<T_IDeviceControl*>(device)->open();
std::cout << std::endl;
void* handle = reinterpret_cast<void*>(device);
//works
std::cout << "cast handle as derived class" << std::endl;
reinterpret_cast<T_Device*>(handle)->getDeviceType();
reinterpret_cast<T_Device*>(handle)->open();
//does not work
std::cout << "cast handle as base class (i.e. interface)" << std::endl;
reinterpret_cast<T_IDeviceInfo*>(handle)->getDeviceType();
reinterpret_cast<T_IDeviceControl*>(handle)->open();
}
The output looks like the following:
T_Device::getDeviceType()
T_Device::open()
cast as derived class
T_Device::getDeviceType()
T_Device::open()
T_Device::getDeviceType()
T_Device::open()
cast handle as derived class
T_Device::getDeviceType()
T_Device::open()
cast handle as base class (i.e. interface)
T_Device::getDeviceType()
T_Device::getDeviceType()
So the problem is the last section. When I cast my generic handle to the base class (interface), where it's function is declared. It seems that it always calls the function of the class which is first in inheritance order. What I've read so far is, that the cast does not now about the class sizes and calculates the offsets wrong. Is this correct? Can you clearify that for me?
I've done some research and noticed the following:
I'm getting the same output, when using static_cast<>()
instead of reinterpret_cast<>()
.
I understand there is a difference in using different casting methods, but I'm confused in which one is the correct for my task.
All posts about c wrapper api propose the approach like in the linked answer above, and using either reinterpret_cast or static_cast.
Is it even possible to cast a generic handle to a base class of a derived class. This would save me a lot of work, since the interfaces are used in different device classes, so I wouldn't need to rewrite the c wrapper function for each device.
Any help is appreciated. Thank you!
I think, static_cast<>()
and reinterpret_cast<>()
simply aren't capable of this kind of transformation you request, since you have a multiple inheritance. Casting a class to its base classes involve some displacement magic if there are multiple base classes.
My advice would be to use a static_cast<>()
to cast to the common class T_Device
and then use a dynamic_cast<>()
to get the real work done:
T_Device *const device = static_cast<T_Device *>(handle);
T_IDeviceInfo *const info = dynamic_cast<T_IDeviceInfo *>(device);
T_IDeviceControl *const control = dynamic_cast<T_IDeviceControl *>(device);
But as I understand, you've got some Device-Classes, like
class T_Keyboard : public T_IDeviceInfo, publibc T_IDeviceControl { /* ... */ };
class T_Mouse : public T_IDeviceInfo, publibc T_IDeviceControl { /* ... */ };
and there's a factory function, for example
void *get_device_handle(const char *name) {
if (strcmp(name, "keyboard") == 0) {
return new T_Keyboard(/* ... */);
} else if (strcmp(name, "mouse") == 0) {
return new T_Mouse(/* ... */);
} else {
return NULL;
}
}
The question is now, how could you write a function like
void open_device(void *handle) {
T_IDeviceControl *const control = /* some casting from handle */
control->open();
}
Right? A possible solution is, to define a common virtual base class for all your interfaces:
class T_Handle {
public:
// By the way: You forgot this important one! In polymorphic classes
// the destructor must be virtual.
virtual ~T_Handle() { }
};
class T_IDeviceInfo : public virtual T_Handle { /* ... */ };
class T_IDeviceControl : public virtual T_Handle { /* ... */ };
The base class T_Handle
has to be virtual, otherwise there are two T_Handle
base classes inside of T_Mouse
and T_Keyboard
. Since both classes T_IDeviceInfo
and T_IDeviceControl
contain an independent base class T_Handle
and up-casting from T_Mouse
to T_Handle
would be ambiguous.
The get_device_handle()
function has to look like this:
void *get_device_handle(const char *name) {
if (strcmp(name, "keyboard") == 0) {
return dynamic_cast<T_Handle *>(new T_Keyboard(/* ... */));
} else if (strcmp(name, "mouse") == 0) {
return dynamic_cast<T_Handle *>(new T_Mouse(/* ... */));
} else {
return NULL;
}
}
The open_device()
function would look like this:
void open_device(void *handle) {
T_Handle *const base = static_cast<T_Handle *>(handle);
T_IDeviceControl *const control = dynamic_cast<T_IDeviceControl *>(base);
control->open();
}