StemObject serves as the base for Stem's object system. The object system is very informal and done largely by these conventions: * Class and instance methods follow the naming convention ClassName_methodName(). * All instance methods receive, as their first parameter, self_type * self. This is the instance on which the method is being called. * Virtual instance methods have a corresponding function pointer class's vtable struct. These function pointers are set to their corresponding function implementations with stemobject_vtable_begin(), one or more stemobject_vtable_entry() statements, and stemobject_vtable_end() at the beginning of their implementation file, just after the definition of stemobject_implementation. * All class headers should contain the following line (adjusted for class name) before any #include statements for other Stem objects: typedef struct ClassName ClassName; * Immediately after the above typedef, each class header should #define ClassName_superclass SuperclassName. * Object instance variables are #defined as ClassName_ivars in the class's header. Subclasses (which should #define their instance variables in the same way) include their superclass's _ivars as the first thing in their own _ivars. * Method overriding is done by implementing a matching Subclass_method in the subclass, and using stemobject_vtable_entry to assign it. Superclass methods can be called with the call_super macro. All super calls are nonvirtual. * All subclass objects must call their superclass's _init() method as the first thing in the subclass's _init(). Use the call_super macro for this. * All subclass objects must call their superclass's _dispose() method as the last thing in the subclass's _dispose() Use the call_super or call_super_virtual macro for this. * All classes must contain the following methods: * _create(): class method which allocates, initializes, and returns an object instance. This method should set the object struct's "allocated" field to true so that the pointer will be freed when StemObject_dispose() is called on it. You can use the stemobject_create_implementation convenience macro as the body of your _create() function. Multiple create functions that take different parameters are valid; they should all start with _create and should all have a corresponding function starting with _init. Classes that should not be instantiated directly can omit the _create() function. * _init(): class method which initializes the passed object's fields, including instance method function pointers. Return true if initialization was successful; call ClassName_dispose() and return false if it wasn't. * If _create()/_init are variadic, there should be variants that take a va_list as their last parameter, named _vcreate() and _vinit(). * _dispose(): instance method which frees any memory allocated by the object, and frees the object itself if and only if it was created by calling _create(). The latter is taken care of by StemObject_dispose(), which should be the last thing in the call chain at the end of your _dispose() function. * Optional: For objects that can be copied, define _copy(), which uses the stemobject_copy_implementation macro in its function body to return a new copy of its only argument, and _initCopy( * self, * original), which manually copies instance variable values from original to self. * Instance variable visibility is specified with the private_ivar and protected_ivar macros. The specified visibility is not enforced by the compiler, but should be used with the following conventions: * Instance variables declared with the private_ivar macro should only be accessed within the implementation of the particular class that declared them. * Instance variables declared with the protected_ivar macro should only be accessed within the implementation of the particular class that declared them or one of its subclasses. * All other instance variables are considered public and may be accessed by any code anywhere. * When instantiating a non-heap-allocated object (calling _init*() directly rather than _create()), use the stemobject_assign_vtable macro before calling the init function. This is automatically done by all _create() functions, and must happen before entering an _init() function to allow calling virtual methods during init. Since subclass structs are a semantically different type than their superclasses, calling a superclass method requires a typecast. There are three macros that handle this: * Each class's vtable contains function pointers that expect self parameters to be of that class's type. This is done by parameterizing the class name in the MyClass_vtable macro. Your code should look like this: #define MyClass_ivars \ int ivar; #define MyClass_vtable(self_type) \ MySuperclass_vtable(self_type) \ void (* method)(self_type * self); stemobject_declare(MyClass) * The compat_type macro can be used to weakly specify the expected type, while evaluating to void *. This bypasses some type safety, as the type cannot be checked for compatibility at compile time, so it should be used sparingly when possible. * The call_super macro provides syntactic sugar and a small degree of extra type safety by convention when calling a superclass method. * The call_super_virtual macro can call the next matching superclass virtual function, skipping steps in the chain if the immediate superclass doesn't implement that virtual function. An example header file declaring a StemObject subclass: -------------------------------------------------- #ifndef __MyObject_H__ #define __MyObject_H__ #ifdef __cplusplus extern "C" { #endif typedef struct MyObject MyObject; #define MyObject_superclass StemObject #include "stemobject/StemObject.h" #define MyObject_ivars \ StemObject_ivars \ int myInstanceVariable; \ void * myAllocation; #define MyObject_vtable(self_type) \ StemObject_vtable(self_type) \ void (* myFunction)(self_type * self, int argument); stemobject_declare(MyObject) MyObject * MyObject_create(void); bool MyObject_init(MyObject * self); MyObject * MyObject_copy(MyObject * self); void MyObject_initCopy(MyObject * self, MyObject * original); void MyObject_dispose(MyObject * self); void MyObject_myFunction(MyObject * self, int argument); #ifdef __cplusplus } #endif #endif -------------------------------------------------- An example of a corresponding implementation file: -------------------------------------------------- #include "PROJECT_NAME/MyObject.h" #include #include #define stemobject_implementation MyObject stemobject_vtable_begin(); stemobject_vtable_entry(dispose); stemobject_vtable_entry(myFunction); stemobject_vtable_end(); MyObject * MyObject_create() { stemobject_create_implementation(MyObject, init) } static void sharedInit(MyObject * self) { call_super(init, self); myInstanceVariable = 0; myAllocation = malloc(1); } bool MyObject_init(MyObject * self) { sharedInit(self); return true; } MyObject * MyObject_copy(MyObject * self) { stemobject_copy_implementation(initCopy) } void MyObject_initCopy(MyObject * self, MyObject * original) { sharedInit(self); self->myInstanceVariable = original->myInstanceVariable; self->myAllocation = malloc(1); memcpy(self->myAllocation, original->myAllocation, 1); } void MyObject_dispose(MyObject * self) { free(myAllocation); call_super(dispose, self); } void MyObject_myFunction(MyObject * self, int argument) { self->myInstanceVariable += argument; } --------------------------------------------------