Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C wrapper for C++ class with stack allocation

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?
like image 776
Pavel Strakhov Avatar asked Feb 17 '16 00:02

Pavel Strakhov


1 Answers

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));; 
like image 137
Alexander Oh Avatar answered Sep 16 '22 20:09

Alexander Oh