1

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!

Jakob
  • 113
  • 1
  • 10

1 Answers1

1

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();
}
phlipsy
  • 2,899
  • 1
  • 21
  • 37
  • Thank your for yout reply! This is working, but wouldn't help me much. Because I have to write a wrapper function for each version of "Device" in which the handle gets first casted to "T_Device" and then to its interface. Means the concrete class must be known, before accesing it's interface function. So the effort would be the same, compared to wrap each interface function for each class, which implements it. Or I'm missing somethin? – Jakob Aug 26 '20 at 08:19
  • Thanks again, for your detiled extension of your answer. I will try to implement your suggestion later. For now I have done a workaround where I do it without multiple inheritance. It fills my needs for now. But I definitly will come back to check your solution. – Jakob Aug 28 '20 at 18:34
  • Just commenting to let you know, I tested your suggestions, and it works fine. Thank you very much. All objects I'm using are know extending a BaseObject to which I cast my Handle (reinterpret_cast, since I'm using a struct as pointer). BaseObject is the casted to my desired Object/Interface via dynamic_cast. I capsulated the casting in a own template Function. Code looks much cleaner now. Thank you! – Jakob Aug 31 '20 at 08:03