28

Let's say we have a C++ library with a class like this:

class TheClass {
public:
  TheClass() { ... }
  void magic() { ... }
private:
  int x;
}

Typical usage of this class would include stack allocation:

TheClass object;
object.magic();

We need to create a C wrapper for this class. The most common approach looks like this:

struct TheClassH;
extern "C" struct TheClassH* create_the_class() {
  return reinterpret_cast<struct TheClassH*>(new TheClass());
}
extern "C" void the_class_magic(struct TheClassH* self) {
  reinterpret_cast<TheClass*>(self)->magic();
}

However, it requires heap allocation, which is clearly not desired for such a small class.

I'm searching for an approach to allow stack allocation of this class from C code. Here is what I can think of:

struct TheClassW {
  char space[SIZEOF_THECLASS];
}
void create_the_class(struct TheClassW* self) {
  TheClass* cpp_self = reinterpret_cast<TheClass*>(self);
  new(cpp_self) TheClass();
}
void the_class_magic(struct TheClassW* self) {
  TheClass* cpp_self = reinterpret_cast<TheClass*>(self);
  cpp_self->magic();
}

It's hard to put real content of the class in the struct's fields. We can't just include C++ header because C wouldn't understand it, so it would require us to write compatible C headers. And this is not always possible. I think C libraries don't really need to care about content of structs.

Usage of this wrapper would look like this:

TheClassW object;
create_the_class(&object);
the_class_magic(&object);

Questions:

  • Does this approach have any dangers or drawbacks?
  • Is there an alternative approach?
  • Are there any existing wrappers that use this approach?
Pavel Strakhov
  • 39,123
  • 5
  • 88
  • 127
  • Does the class object do any memory allocation in `magic()`? Or is the size of the class itself the only allocation you are worried about? – RyanP Feb 17 '16 at 00:52
  • If the class uses heap allocation internally, it's totally fine (and nothing can be done anyway). I just need the class itself to be stack-allocated. – Pavel Strakhov Feb 17 '16 at 00:57
  • 2
    You might want to put `static_assert(sizeof(TheClassW) == sizeof(TheClass))` in the wrapper source file. – user253751 Feb 17 '16 at 01:18
  • 2
    In general you'd also need a `destruct_the_object(struct TheClassW* self) { reinterpret_cast(self)->~TheClass(); }`. Plus some policy on handling exceptions, see for example [Code reuse in exception handling](http://stackoverflow.com/questions/847279/code-reuse-in-exception-handling). – dxiv Feb 17 '16 at 02:37
  • The current answers don't seem to account for the destructor being called, so resources may be lost/leaked. Does that matter to you? – mksteve Feb 21 '16 at 11:05
  • Destructors can only be called manually in C, and in other aspects a destructor is just another member function and can be wrapped in the same way, so I don't think it's a special case. – Pavel Strakhov Feb 21 '16 at 11:24
  • Don't know how relevant this is, but in the early days of c++, there were no compilers for c++. There was a precompiler that would take the c++ code and translate it into C, then the C code would be compiled. This was very long ago and I'm not sure if the precompiler still exists but it might be worth a look. – Bing Bang Feb 23 '16 at 17:11
  • 3
    You could be using alloca() to allocate room on the stack in your C wrapper and then placement new() for the class on the C++-end to construct the object there. – tofro Feb 24 '16 at 08:18

7 Answers7

14

You can use placement new in combination of alloca to create an object on the stack. For Windows there is _malloca. The importance here is that alloca, and malloca align memory for you accordingly and wrapping the sizeof operator exposes the size of your class portably. Be aware though that in C code nothing happens when your variable goes out of scope. Especially not the destruction of your object.

main.c

#include "the_class.h"
#include <alloca.h>

int main() {
    void *me = alloca(sizeof_the_class());

    create_the_class(me, 20);

    if (me == NULL) {
        return -1;
    }

    // be aware return early is dangerous do
    the_class_magic(me);
    int error = 0;
    if (error) {
        goto fail;
    }

    fail:
    destroy_the_class(me);
}

the_class.h

#ifndef THE_CLASS_H
#define THE_CLASS_H

#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
    class TheClass {
    public:
        TheClass(int me) : me_(me) {}
        void magic();
        int me_;
    };

extern "C" {
#endif

size_t sizeof_the_class();
void *create_the_class(void* self, int arg);
void the_class_magic(void* self);
void destroy_the_class(void* self);

#ifdef __cplusplus
}
#endif //__cplusplus


#endif // THE_CLASS_H

the_class.cc

#include "the_class.h"

#include <iostream>
#include <new>

void TheClass::magic() {
    std::cout << me_ << std::endl;
}

extern "C" {
    size_t sizeof_the_class() {
        return sizeof(TheClass);
    }

    void* create_the_class(void* self, int arg) {
        TheClass* ptr = new(self) TheClass(arg);
        return ptr;
    }

    void the_class_magic(void* self) {
        TheClass *tc = reinterpret_cast<TheClass *>(self);
        tc->magic();
    }

    void destroy_the_class(void* self) {
        TheClass *tc = reinterpret_cast<TheClass *>(self);
        tc->~TheClass();
    }
}

edit:

you can create a wrapper macro to avoid separation of creation and initialization. you can't use do { } while(0) style macros because it will limit the scope of the variable. There is other ways around this but this is highly dependent on how you deal with errors in the code base. A proof of concept is below:

#define CREATE_THE_CLASS(NAME, VAL, ERR) \
  void *NAME = alloca(sizeof_the_class()); \
  if (NAME == NULL) goto ERR; \

// example usage:
CREATE_THE_CLASS(me, 20, fail);

This expands in gcc to:

void *me = __builtin_alloca (sizeof_the_class()); if (me == __null) goto fail; create_the_class(me, (20));;
eldruin
  • 332
  • 3
  • 16
Alexander Oh
  • 24,223
  • 14
  • 73
  • 76
  • IMO `alloca` is the most reasonable solution. Just wrap it in a macro so that `create_class` does both the allocation and the construction. – sbabbi Feb 25 '16 at 12:08
  • @sbabbi actually I thought about putting it in a macro but decided against it, as I thought that people should decide on their own whether it should be wrapped in a macro or not. But I can easily add it. – Alexander Oh Feb 25 '16 at 12:43
6

There are alignment dangers. But maybe not on your platform. Fixing this may require platform specific code, or C/C++ interop that is not standardized.

Design wise, have two types. In C, it is struct TheClass;. In C++, struct TheClass has a body.

Make a struct TheClassBuff{char buff[SIZEOF_THECLASS];};

TheClass* create_the_class(struct TheClassBuff* self) {
  return new(self) TheClass();
}

void the_class_magic(struct TheClass* self) {
  self->magic();
}

void the_class_destroy(struct TheClass* self) {
  self->~TheClass();
}

C is supposed to make the buff, then create a handle from it and interact using it. Now usually that isn't required as reinterpreting pointer to theclassbuff will work, but I think that is undefined behaviour technically.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • If I use two types (struct TheClass and TheClassBuff), what benefit does that provide? Which platforms can cause alignment problems and what can be done to prevent them? – Pavel Strakhov Feb 18 '16 at 01:56
  • @pavel I do not know what platforms could have alignment problems? If your data is a massively over aligned char, using an array of 1 char and construcing it in there is unlikely to work. As for the two types: accessing the object created in the buffer via anything except the return value of new is UB, and may trip strict aliasing optimizations even if the address works out the same. So the big advantage is avoiding UBL once you do UB, the code must be validated on every compiler upgrade and every platform built for, which sucks. – Yakk - Adam Nevraumont Feb 18 '16 at 04:22
  • 1
    Hmm. Std align, plus make the buffer be sizeof + alignof -1, would be portable. Note that this is another reason you may need that ptr. – Yakk - Adam Nevraumont Feb 18 '16 at 04:27
  • Why should the buffer be "sizeof + alignof -1" instead of "sizeof + alignof"? – Pavel Strakhov Feb 18 '16 at 12:27
  • 2
    @PavelStrakhov Because if you need 4 byte alignment and you are 4 bytes in size, a buffer of size (4+4-1)=7 is sufficient. The offsets required could be 0, 1, 2, or 3. An offset of size 4 means that 0 would also do. The maximal offset, 3 (which equals alignof -1), plus the size, is the size of the buffer required to guarantee that `std::align` can produce data of that alignment from an unaligned source buffer. As an aside, this means that data with an alignment of 1 does not require extra space, as one might expect. – Yakk - Adam Nevraumont Feb 18 '16 at 14:24
  • Too see this cause a problem in an x64/x86 chip, try making your C++ struct be a pointer, and your C struct be `struct test{ char unused; TheClassBuff buff; }` with any pragma packing. This may result in the buff being misaligned. Which could cause performance problems (and on other systems with hard alignment requirements, trap) – Yakk - Adam Nevraumont Feb 18 '16 at 14:27
6

Here is another approach, which may or may not be acceptable, depending on the application specifics. Here we basically hide the existence of TheClass instance from C code and encapsulate every usage scenario of TheClass in a wrapper function. This will become unmanageable if the number of such scenarios is too large, but otherwise may be an option.

The C wrapper:

extern "C" void do_magic()
{
  TheClass object;
  object.magic();
}

The wrapper is trivially called from C.

Update 2/17/2016:

Since you want a solution with a stateful TheClass object, you can follow the basic idea of your original approach, which was further improved in another answer. Here is yet another spin on that approach, where the size of the memory placeholder, provided by the C code, is checked to ensure it is sufficiently large to hold an instance of TheClass.

I would say that the value of having a stack-allocated TheClass instance is questionable here, and it is a judgement call depending on the application specifics, e.g. performance. You still have to call the de-allocation function, which in turn calls the destructor, manually, since it is possible that TheClass allocates resources that have to be released.

However, if having a stack-allocated TheClass is important, here is another sketch.

The C++ code to be wrapped, along with the wrapper:

#include <new>
#include <cstring>
#include <cstdio>

using namespace std;

class TheClass {
public:
  TheClass(int i) : x(i) { }
  // cout doesn't work, had to use puts()
  ~TheClass() { puts("Deleting TheClass!"); }
  int magic( const char * s, int i ) { return 123 * x + strlen(s) + i; }
private:
  int x;
};

extern "C" TheClass * create_the_class( TheClass * self, size_t len )
{
  // Ensure the memory buffer is large enough.
  if (len < sizeof(TheClass)) return NULL;
  return new(self) TheClass( 3 );
}

extern "C" int do_magic( TheClass * self, int l )
{
  return self->magic( "abc", l );
}

extern "C" void delete_the_class( TheClass * self )
{
  self->~TheClass();  // 'delete self;' won't work here
}

The C code:

#include <stdio.h>
#define THE_CLASS_SIZE 10

/*
   TheClass here is a different type than TheClass in the C++ code,
   so it can be called anything else.
*/
typedef struct TheClass { char buf[THE_CLASS_SIZE]; } TheClass;

int do_magic(TheClass *, int);
TheClass * create_the_class(TheClass *, size_t);
void delete_the_class(TheClass * );

int main()
{
  TheClass mem; /* Just a placeholder in memory for the C++ TheClass. */
  TheClass * c = create_the_class( &mem, sizeof(TheClass) );
  if (!c) /* Need to make sure the placeholder is large enough. */
  {
    puts("Failed to create TheClass, exiting.");
    return 1;
  }
  printf("The magic result is %d\n", do_magic( c, 232 ));
  delete_the_class( c );

  return 0;
}

This is just a contrived example for illustration purposes. Hopefully it is helpful. There may be subtle problems with this approach, so testing on your specific platform is highly important.

A few additional notes:

  • THE_CLASS_SIZE in the C code is just the size of a memory buffer in which a C++'s TheClass instance is to be allocated; we are fine as long as the size of the buffer is sufficient to hold a C++'s TheClass

  • Because TheClass in C is just a memory placeholder, we might just as well use a void *, possibly typedef'd, as the parameter type in the wrapper functions instead of TheClass. We would reinterpret_cast it in the wrapper code, which would actually make the code clearer:
    pointers to C's TheClass are essentially reinterpreted as C++'s TheClass anyway.

  • There is nothing to prevent C code from passing a TheClass* to the wrapper functions that doesn't actually point to a C++'s TheClass instance. One way to solve this is to store pointers to properly initialized C++ TheClass instances in some sort of a data structure in the C++ code and return to the C code handles that can be used to look up these instances.
  • To use couts in the C++ wrapper we need to link with the C++ standard lib when building an executable. For example, if the C code is compiled into main.o and C++ into lib.o, then on Linux or Mac we'd do gcc -o junk main.o lib.o -lstdc++.
Anatoli P
  • 4,791
  • 1
  • 18
  • 22
  • This could only work for stateless objects. For example, it wouldn't allow for a sequence of `do_magic(); do_more_magic(); collect_applause();` on the _same_ object. – dxiv Feb 17 '16 at 04:15
  • This sequence is a usage scenario that would have to be encapsulated in a separate wrapper function with this approach. That's the drawback: the more scenarios we have, the less attractive this approach is. – Anatoli P Feb 17 '16 at 04:29
  • 1
    My goal is to create wrapper for universal use, so this approach can't be applied. – Pavel Strakhov Feb 18 '16 at 01:54
3

It worth to keep each piece of knowledge in a single place, so I would suggest to make a class code "partially readable" for C. One may employ rather simple set of macro definitions to enable it to be done in short and standard words. Also, a macro may be used to invoke constructor and destructor at the beginning and the end of stack-allocated object's life.

Say, we include the following universal file first into both C and C++ code:

#include <stddef.h>
#include <alloca.h>

#define METHOD_EXPORT(c,n) (*c##_##n)
#define CTOR_EXPORT(c) void (c##_construct)(c* thisPtr)
#define DTOR_EXPORT(c) void (c##_destruct)(c* thisPtr)

#ifdef __cplusplus
#define CL_STRUCT_EXPORT(c)
#define CL_METHOD_EXPORT(c,n) n
#define CL_CTOR_EXPORT(c) c()
#define CL_DTOR_EXPORT(c) ~c()
#define OPT_THIS
#else
#define CL_METHOD_EXPORT METHOD_EXPORT
#define CL_CTOR_EXPORT CTOR_EXPORT
#define CL_DTOR_EXPORT DTOR_EXPORT
#define OPT_THIS void* thisPtr,
#define CL_STRUCT_EXPORT(c) typedef struct c c;\
     size_t c##_sizeof();
#endif

/* To be put into a C++ implementation coce */
#define EXPORT_SIZEOF_IMPL(c) extern "C" size_t c##_sizeof() {return sizeof(c);}
#define CTOR_ALIAS_IMPL(c) extern "C" CTOR_EXPORT(c) {new(thisPtr) c();}
#define DTOR_ALIAS_IMPL(c) extern "C" DTOR_EXPORT(c) {thisPtr->~c();}
#define METHOD_ALIAS_IMPL(c,n,res_type,args) \
    res_type METHOD_EXPORT(c,n) args = \
        call_method(&c::n)

#ifdef __cplusplus
template<class T, class M, M m, typename R, typename... A> R call_method(
    T* currPtr, A... args)
{
    return (currPtr->*m)(args...);
}
#endif

#define OBJECT_SCOPE(t, v, body) {t* v = alloca(t##_sizeof()); t##_construct(v); body; t##_destruct(v);}

Now we can declare our class (the header is useful both in C and C++, too)

/* A class declaration example */
#ifdef __cplusplus
class myClass {
private:
    int y;
    public:
#endif
    /* Also visible in C */
    CL_STRUCT_EXPORT(myClass)
    void CL_METHOD_EXPORT(myClass,magic) (OPT_THIS int c);
    CL_CTOR_EXPORT(myClass);
    CL_DTOR_EXPORT(myClass);
    /* End of also visible in C */
#ifdef __cplusplus

};
#endif

Here is the class implementation in C++:

myClass::myClass() {std::cout << "myClass constructed" << std::endl;}
CTOR_ALIAS_IMPL(myClass);
myClass::~myClass() {std::cout << "myClass destructed" << std::endl;}
DTOR_ALIAS_IMPL(myClass);
void myClass::magic(int n) {std::cout << "myClass::magic called with " << n << std::endl;}

typedef void (myClass::* myClass_magic_t) (int);
void (*myClass_magic) (myClass* ptr, int i) = 
    call_method<myClass,myClass_magic_t,&myClass::magic,void,int>;

and this is a using C code example

main () {
    OBJECT_SCOPE(myClass, v, {
        myClass_magic(v,178);
        })
}

It's short and working! (here's the output)

myClass constructed
myClass::magic called with 178
myClass destructed

Note that a variadic template is used and this requires c++11. However, if you don't want to use it, a number of fixed-size templates ay be used instead.

AndreyS Scherbakov
  • 2,674
  • 2
  • 20
  • 27
1

Here's how one might do it safely and portably.

// C++ code
extern "C" {
 typedef void callback(void* obj, void* cdata);

 void withObject(callback* cb, void* data) {
  TheClass theObject;
  cb(&theObject, data);
 }
}

// C code:

struct work { ... };
void myCb (void* object, void* data) {
   struct work* work = data;
   // do whatever 
}

// elsewhere
  struct work work;
  // initialize work
  withObject(myCb, &work);
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
1

What I did in alike situation is something like: (I omit static_cast, extern "C")

class.h:

class TheClass {
public:
  TheClass() { ... }
  void magic() { ... }
private:
  int x;
}

class.cpp

<actual implementation>

class_c_wrapper.h

void* create_class_instance(){
    TheClass instance = new TheClass();
}

void delete_class_instance(void* instance){
    delete (TheClass*)instance;
}

void magic(void* instance){
    ((TheClass*)instance).magic();
}

Now, you stated that you need stack allocation. For this I can suggest rarely used option of new: placement new. So you'd pass additional parameter in create_class_instance() that is pointing to an allocated buffer enough to store class instance, but on stack.

rezdm
  • 165
  • 1
  • 11
0

This is how I would solve the issue (basic idea is to let interprete C and C++ the same memory and names differently):
TheClass.h:

#ifndef THECLASS_H_
#define THECLASS_H_

#include <stddef.h>

#define SIZEOF_THE_CLASS 4

#ifdef __cplusplus
class TheClass
{
public:
    TheClass();
    ~TheClass();
    void magic();

private:
    friend void createTheClass(TheClass* self);
    void* operator new(size_t, TheClass*) throw ();
    int x;
};

#else

typedef struct TheClass {char _[SIZEOF_THE_CLASS];} TheClass;

void create_the_class(struct TheClass* self);
void the_class_magic(struct TheClass* self);
void destroy_the_class(struct TheClass* self);

#endif

#endif /* THECLASS_H_ */

TheClass.cpp:

TheClass::TheClass()
    : x(0)
{
}

void* TheClass::operator new(size_t, TheClass* self) throw ()
{
    return self;
}

TheClass::~TheClass()
{
}

void TheClass::magic()
{
}

template < bool > struct CompileTimeCheck;
template < > struct CompileTimeCheck < true >
{
    typedef bool Result;
};
typedef CompileTimeCheck< SIZEOF_THE_CLASS == sizeof(TheClass) >::Result SizeCheck;
// or use static_assert, if available!

inline void createTheClass(TheClass* self)
{
    new (self) TheClass();
}

extern "C"
{

void create_the_class(TheClass* self)
{
    createTheClass(self);
}

void the_class_magic(TheClass* self)
{
    self->magic();
}

void destroy_the_class(TheClass* self)
{
    self->~TheClass();
}

}

The createTheClass function is for friendship only - I wanted to avoid the C wrapper functions to be publicly visible within C++. I caught up the array variant of the TO, because I consider this better readable than the alloca approach. Tested with:
main.c:

#include "TheClass.h"

int main(int argc, char*argv[])
{
    struct TheClass c;
    create_the_class(&c);
    the_class_magic(&c);
    destroy_the_class(&c);
}
Aconcagua
  • 24,880
  • 4
  • 34
  • 59