Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass C++ object (with possible multiple virtual inheritance) through a C ABI via void pointer

I have some concerns about the safety of type conversions I'm designing an abstract interface, witch will be supported by plugins exporting an object orientated C ABI, i.e. pointers to objects and C-style functions of the form func(void *this, ...) rather than C++ style member functions these will then be packed into a struct representing the objects implementation. However some of my underlying frameworks, use multiple virtual inheritance.

Simplified example

class A
{
    public:
        virtual void doA()
}

class B
{
    public:
        virtual void doB()
}

class C : public A, public B
{
    public:
        virtual void doA()
        virtual void doB()
}

struct impA
{
    (*doA)(void *self);
}

struct impB
{
    (*doB)(void *self);
}

struct impC
{
    (*doA)(void *self);
    (*doB)(void *self);
}

void * AfromC(void *v) {
    C*c = reinterpret_cast<C*>(v); // Known to be C* type
    return static_cast<void*>(static_cast<A*>(c)); // method 1
    return reinterpret_cast<void*>(static_cast<A*>(c)); // method 2

    //method 3 & 4
    C** c = static_cast<C**>(v); // Known to be C* type
    return static_cast<void*>(&static_cast<A*>(*c)); // method 3
    return static_cast<void*>(static_cast<A**>(c)); // method 4
}

/////////// main code

class A
{
    public:
        void doA() { imp.doA(self); }
    private:
        impA imp;
        void *self;
}

class B
{
    public:
        void doB() { imp.doB(self); }
    private:
        impB imp;
        void *self;
}

Consider AfromC, I have 4 possible methods of getting a pointer that I can safely pass through a C ABI, I want to known the consideration for these different methods, my preference would be method 1.

I'm not sure if all these methods are legal or safe.

Note: Object will always be access by functions in the binary from which they are created/destroyed they return/accept other objects handled by them self or C-style data types (up to structs of POD)

While I've found mention of such things on the net, they are all about people having problems as a result of converting to void i.e. A* a= static_cast<A*>(static_cast<void*>(c)) // c -> C* which is to be expected as this doesn't correct the vtable and the solutions is to use the abstract base type (this wron't work for me as I need to pass through the C ABI), however I've also heard mention of virtual pointers being larger than normal pointers hence my reason for considering methods 3 and 4, as this would be a normal pointer to the larger pointer and thus safe even for types with larger pointers.

So my main question is will method 1 work with out problem? Aslo could I safely define a template function along the lines of template <typename T, typename U> void * void_cast(U v) { static_cast<void *>(static_cast<T>(v)); } to simplify plugin code. Finally if method 1 is correct why? and can any of the methods be used?

like image 486
Glen Fletcher Avatar asked Oct 25 '25 19:10

Glen Fletcher


1 Answers

The rule is that you can convert back and forth from a pointer to object to a pointer to its base class, and from a pointer to object to a void *. But there are no guarantees that all those pointers keep same value (nor even same representation)!

Said differently with examples where C is derived from A:

C* c = new C;
A* a = static_cast<A*>(c);   // legal
C* c1 = static_cast<C*>(a);  // ok c1 == c guaranteed

void *vc = static_cast<void *>(c); // legal
C* c2 = static_cast<C*>(vc); // ok, c2 == c guaranteed

void *va = static_cast<void *>(a); // legal, but va == vc is not guaranteed

a2 = static_cast<A*>(vc);    // legal, but a2 == a not guaranteed 
                             //  and dereferencing a2 is Undefined Behaviour

That means that if v is built as void *v = static_cast<void *>(c); and then passed to your AfromC method static_cast<A*>(v) may not point to a valid object. And both methods (1) and (2) are no-op because you cast from void* to a pointer to obj and back which is required to get original value.

For method (4), but you cast a pointer to void to a pointer to pointer, from pointer to pointer to pointer to pointer again and then back to void. As 3.9.2 Compound types [basic.compound] declares:

3 ...Pointers to layout-compatible types shall have the same value representation and alignment requirements...

As all pointers are layout compatible types, the second operation should not change the value and we are back in the no-op of method (1) and (2)

Method (3) should not even compile, because you take the address of a static_cast and that is not a lvalue.

TL/DR: methods (1), (2) and (4) are no-op, meaning that you return the input value unchanged and method (3) is illegal because & operator requires a lvalue.

The only realiable way to convert a void* pointing to a C object to something that could be safely converted to a A* is:

void * AfromC(void *v) {
    C* c = static_cast<C*>(v);   // v shall be static_cast<void>(ptr_to_C)
    A* a = static_cast<A*>(c);
    return static_cast<void *>(a); // or return a; with the implicit void * convertion
}

or as a single line expression

void * AfromC(void *v) {
    return static_cast<A*>(static_cast<C*>(v));
}
like image 109
Serge Ballesta Avatar answered Oct 27 '25 09:10

Serge Ballesta



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!