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:
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.
#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); }
#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
#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));;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With