A flexible cross-language ABI-stable OOP C header
There is a single polymorphic object type Object*
.
It can be multiple classes.
Object* dog = Dog_create();
Animal_is(dog); // true
Dog_is(dog); // true
You can call an object's virtual methods simply by calling a C function.
Dog_bury(dog, bone);
Animal_speak(dog); // Woof!
// Call direct method without virtual method lookup
// Similar to dog->Animal::speak() in C++
Animal_speak_mdirect(dog); // Hello
// Or use macros if you prefer
CALL(dog, Dog, bury, bone);
CALL(dog, Animal, speak); // Woof!
CALL_DIRECT(dog, Animal, speak); // Hello
Each class's data is encapsulated in a private/opaque struct unless exposed by getters/setters, which can be virtual or non-virtual.
// legs property is stored in Animal class data
Animal_legs_get(dog); // 4, since Dog calls Animal_legs_set(dog, 4) upon specialization
Animal_legs_set(dog, 5); // Dog can override Animal_legs_set() to validate the value and perform custom behavior
// name property is stored in Dog class data
Dog_name_set(dog, "Fido");
// Or use macros if you prefer
GET(dog, Animal, legs); // 4
SET(dog, Animal, legs, 5);
SET(dog, Dog, name, "Fido");
You can specialize an object's type after instantiation, even if classes are unrelated.
Poodle_specialize(dog); // dog is now an Animal, Dog, and Poodle
Poodle_is(dog); // true
// Objects cannot be un-specialized, since doing so could leave objects with invalid/impossible state.
// Or use macros if you prefer
SPECIALIZE(dog, Poodle);
IS(dog, Poodle); // true
Calling methods of the wrong class gracefully returns default/error values.
Bird_is(dog); // false
Bird_fly(dog); // Nothing happens, no-op
Bird_wingspan_get(dog); // Returns a default/error value defined by Bird_wingspan_get() implementation, such as -1
Objects are reference-counted, and each shared owner must release their reference to avoid a memory leak.
When an object's last reference is released, each class's finalize()
function is called in reverse order of specialization, where virtual methods are allowed to be called.
Then each class's free()
is called in reverse order, where virtual methods are not allowed to be called.
Object_release(dog);
You can create your own object classes by including Object.h
and using its macros.
The Animal
and Dog
classes above can be declared by this .h
header.
// You can add constructor/initialization arguments if needed
DECLARE_CLASS(Animal, ());
DECLARE_METHOD_CONST(Animal, speak, void, ());
DECLARE_ACCESSORS(Animal, legs, int);
/** A domesticated wolf. Specializes Animal. */
DECLARE_CLASS(Dog, ());
DECLARE_METHOD(Dog, bury, void, (Object* thing));
DECLARE_ACCESSORS(Dog, name, const char*);
DECLARE_METHOD_CONST_OVERRIDE(Dog, speak, void, ());
See examples/Animal.c for a possible implementation using DEFINE_*
macros.
Without breaking your library's ABI (application binary interface), you can:
- Add, remove, and modify class data.
- Add virtual and non-virtual methods, getters, and setters.
- Convert non-virtual methods to virtual methods.
- Change class hierarchy, such as inserting a
Mammal
class betweenDog
andAnimal
, or makingAnimal
specializeOrganism
, or makingDog
not based onAnimal
.- Note that
Animal_speak(dog)
will do nothing (no-op) ifDog
is no longer based onAnimal
.
- Note that
Without breaking the ABI, you cannot:
- Remove classes.
- Remove virtual and non-virtual methods, getters, and setters.
- Convert virtual methods to non-virtual methods.
- Change method signatures, such as adding/removing an argument.
Each virtual method such as DECLARE_METHOD(Foo, bar, int, (int n))
declares the following C functions.
int Foo_bar(Object* self, int n);
int Foo_bar_mdirect(Object* self, int n);
typedef int (*Foo_bar_m)(Object* self, int n);
Foo_bar_m Foo_bar_mget(const Object* self);
void Foo_bar_mset(Object* self, Foo_bar_m m);
Any language/environment with a C FFI interface can call these functions to interact with your library, including C++, Go, Rust, Zig, Java, C#, Python, PHP, Node, Ruby, Julia, Lua, etc.
The Object struct and each class's data structs are private/opaque and must not be accessed except by these methods.
Each Object is a map/dictionary of pointers to private data structs. This makes VCV Object more flexible, ABI-stable, and language-compatible than C++ classes, but it must perform more function calls and pointer dereferences than C++.
Rough benchmarking suggests that a virtual method call is about 5x slower than on C++. We recommend using this library for applications where flexibility is important but navigating the OOP hierarchy is not a bottleneck, such as UI widgets or plugin hosting.
- Object.h contains macros to declare and define your classes and methods, as well as runtime function declarations.
- Object.cpp is a possible C++ implementation of the (tiny) runtime. Feel free to port this to C, Go, etc if you have a favorite map/dictionary implementation.
- examples/ contains example programs and helpful classes you might need in your library.
The contents of this repository are released into the public domain.
If you use this library in your project, we'd love to hear about it!