C ++ to C Wrap: Convert to Basic

I am wrapping a simple C ++ inheritance hierarchy in an “object-oriented” C. I am trying to find out if there are any problems when handling pointers to C ++ objects as pointers to opaque C structures. In particular, under what circumstances can transforming a derivative base cause problems?

The classes themselves are relatively complex, but the hierarchy is shallow and uses single inheritance:

// A base class with lots of important shared functionality
class Base {
    public:
    virtual void someOperation();
    // More operations...

    private:
    // Data...
};

// One of several derived classes
class FirstDerived: public Base {
    public:
    virtual void someOperation();
    // More operations...

    private:
    // More data...
};

// More derived classes of Base..

I plan to expose this to C clients through the following, fairly standard object oriented C:

// An opaque pointers to the types
typedef struct base_t base_t;
typedef struct first_derived_t first_derived_t;

void base_some_operation(base_t* object) {
     Base* base = (Base*) object;
     base->someOperation();
}

first_derived_t* first_derived_create() {
     return (first_derived_t*) new FirstDerived();
}

void first_derived_destroy(first_derived_t* object) {
     FirstDerived* firstDerived = (FirstDerived*) object;
     delete firstDerived;
}

C clients only pass pointers to basic C ++ objects and can only manipulate them through function calls. That way, the client can finally do something like:

first_derived_t* object = first_derived_create();
base_some_operation((base_t*) object); // Note the derived-to-base cast here
...

and have a virtual call to FirstDerived :: someOperation () as expected.

standard-layout, . ?

, (++ C wrapper), .

+5
4
// An opaque pointers to the types
typedef struct base_t base_t;
typedef struct first_derived_t first_derived_t;

// **********************//
// inside C++ stub only. //
// **********************//

// Ensures you always cast to Base* first, then to void*,
// then to stub type pointer.  This enforces that you'll
// get consistent a address in presence of inheritance.
template<typename T>
T * get_stub_pointer ( Base * object )
{
     return reinterpret_cast<T*>(static_cast<void*>(object));
}

// Recover (intermediate) Base* pointer from stub type.
Base * get_base_pointer ( void * object )
{
     return reinterpret_cast<Base*>(object);
}

// Get derived type pointer validating that it actually
// the right type.  Returs null pointer if the type is
// invalid.  This ensures you can detect invalid use of
// the stub functions.
template<typename T>
T * get_derived_pointer ( void * object )
{
    return dynamic_cast<T*>(get_base_pointer(object));
}

// ***********************************//
// public C exports (stub interface). //
// ***********************************//

void base_some_operation(base_t* object)
{
     Base* base = get_base_pointer(object);
     base->someOperation();
}

first_derived_t* first_derived_create()
{
     return get_stub_pointer<first_derived_t>(new FirstDerived());
}

void first_derived_destroy(first_derived_t* object)
{
     FirstDerived * derived = get_derived_pointer<FirstDerived>(object);
     assert(derived != 0);

     delete firstDerived;
}

, , .

first_derived_t* object = first_derived_create();
base_some_operation((base_t*) object);

, base_t* void*, Base*. , . :

  • FirstDerived*
  • Base* ( static_cast<Base*>)
  • void* ( static_cast<void*>)
  • first_derived_t* ( reinterpret_cast<first_derived_t*>)
  • base_t* ( (base_t*), ++ reinterpret_cast<base_t*>)
  • void* ( static_cast<void*>)
  • Base* ( reinterpret_cast<Base*>)

, FirstDerived, :

  • FirstDerived* ( dynamic_cast<FirstDerived*>)
+3

, , C- ++. , , extern "C", void * :

// library.h, for C clients

typedef void * Handle;

extern "C" Handle create_foo();
extern "C" void destroy_foo(Handle);

extern "C" int magic_foo(Handle, char const *);

++:

#include "library.h"
#include "foo.hpp"

Handle create_foo()
{
    Foo * p = nullptr;

    try { p = new Foo; }
    catch (...) { p = nullptr; }

    return p
}

void destroy_foo(Handle p)
{
    delete static_cast<Foo*>(p);
}

int magic_foo(Handle p, char const * s)
{
    Foo * const f = static_cast<Foo*>(p);

    try
    {
        f->prepare();
        return f->count_utf8_chars(s);
    }
    catch (...)
    {
        return -1;
        errno = E_FOO_BAR;
    }
}

, - C!

+4

, (, ). , C, ++. ++; , , . [, delete.] , someOperation() Derived* Base*. Derived , someOtherOperation, Derived* .

: "BaseDerived.H"

#ifdef __cplusplus
extern "C"
{
#endif
    typedef struct Base Base;
    typedef struct Derived Derived;

    Derived* createDerived();
    Base* createBase();
    Base* upcastToBase(Derived* derived);
    Derived* tryDownCasttoDerived(Base* base);
    void someOperation(Base* base);
void someOtherOperation(Derived* derived);
#ifdef __cplusplus
}
#endif

: "BaseDerived.CPP"

#include "BaseDerived.H"
struct Base 
{
    virtual void someOperation()
    {
        std::cout << "Base" << std::endl;
    }
};
struct Derived : public Base
{
public:
    virtual void someOperation()
    {
        std::cout << "Derived" << std::endl;
    }
private:
};

Derived* createDerived()
{
    return new Derived;
}

Base* createBase()
{
    return new Base;
}

Base* upcastToBase(Derived* derived)
{
    return derived;
}

Derived* tryDownCasttoDerived(Base* base)
{
    return dynamic_cast<Derived*>(base);
}

void someOperation(Base* base)
{
    base->someOperation();
}

void someOperation(Derived* derived)
{
    derived->someOperation();
}
+1

, :

first_derived_t* object = first_derived_create();
base_some_operation((base_t*) object); // Note the derived-to-base cast here
...

C- . C , ++ casts , , - C.

( ?) . -, , C , Base* - . , ++ dynamic_cast, .

( C-, , . , , (Derived1*) derived2_ptr . - - - C-.)

C -

struct base_t_ptr {
    void * this_; // holds the Base pointer
};
typedef struct {
    struct base_t_ptr get_base;
} derived_t_ptr;

- . , object.get_base :

first_derived_t_ptr object = first_derived_create();
base_some_operation(object.get_base);

base_some_operation

extern "C" base_some_operation(struct base_t_ptr);

This will be completely type safe, as you cannot pass the_1_t_ptr derivative to this function without going through the data element .get_base. It will also help your C code learn a little about types and which conversions are valid - you do not want to accidentally convert Derived1 to Derived2.

Then, when implementing non-virtual methods defined only in a derived class, you will need something like:

extern "C" void derived1_nonvirtual_operation(struct derived1_t_ptr); // The C-style interface. Type safe.

void derived1_nonvirtual_operation(struct derived1_t_ptr d) {
    // we *know* this refers to a Derived1 type, so we can trust these casts:
    Base * bp = reinterpret_cast<Base*>(d.get_base.this_);
    Derived1 *this_ = dynamic_cast<Derived1*>;
    this_ -> some_operation();
}
0
source

All Articles