I come from an OOP background, and have recently delved into C and embedded systems.
As an exercise I wanted to see if I could wrap some OOP concepts into a macro header.
It works as I intended, is fairly simple to use and get a grip on if you know OOP terminology / concepts.
Would love any feedback to see if I’m doing something stupid.
At the moment it’s only heap allocation. I will update in future for stack based struct objects, but it only requires the coder write 1 additional function to implement.
It’s >C/GNU99 and requires P99 for the variadic macros (I’ve yet to copy those little bits over).
#ifndef OOP_MAIN_H
#define OOP_MAIN_H
#ifndef _STDLIB_H
#include <stdlib.h>
#endif
#ifndef P99_IF_H_
#include "p99/p99_if.h"
#endif
/**
* grab an interface definition by calling it's macro.
* @param i Interface name
*/
#define eIMPLEMENTS(i) eINTERFACE_##i()
/**
* add a parent class struct to gain access to it's public methods
* @param p Struct name
* @param n Property name
*/
#define eEXTENDS(p,n) struct p n
/**
* helper macro to denote that this parent is upcastable (this macro must be first element of containing
* struct for this to be true)
* @param p Struct name
* @param n Property name
*/
#define eDIR_EXTENDS(p,n) eEXTENDS(p, n)
/**
* Instantiate an object 'o*' of type 'c' by using function 'c_instatiate()'
* @param <classtype_t> c
* @param var o Object variable name
* @param ... any further arguments
*/
#define eNEW_INS(c,o, ...) P99_IF_EMPTY(__VA_ARGS__) (c##_instantiate(o)) (c##_instantiate(o, __VA_ARGS__))
/**
* Call allocation method and imediately fire instatiation function for heap object
* @param <classtype_t> c
* @param var o Object variable name
* @param ... any further arguments
*/
#define eNEW(c,o, ...) struct c*o = (struct c *)malloc(sizeof(struct c)); eNEW_INS(c,o, __VA_ARGS__)
/**
* public property DECLARATION
* @param t Type
* @param p Property name
*/
#define ePROP_DEC(t, p) t p
/**
* public property DEFINITION
* @param p Property name
* @param v Value
*/
#define ePROP_DEF(p, v) self->p = v
/**
* private property PUBLIC function-pointer DECLARATIONS for public struct
* @param t Type
* @param p Property name
* @param m get/set/getset
*/
#define ePRIV_PROP_DEC_get(t,p) t (*get_##p)(void * eOBJ)
#define ePRIV_PROP_DEC_set(t,p) void (*set_##p)(void * eOBJ, t v)
#define ePRIV_PROP_DEC_getset(t,p) ePRIV_PROP_DEC_get(t,p); ePRIV_PROP_DEC_set(t,p)
#define ePRIV_PROP_DEC_PUB(t, p, m) ePRIV_PROP_DEC_##m(t,p)
/**
* private property PRIVATE function-pointer DECLARATIONS for private struct
* @param t Type
* @param p Property name
* @param m get/set/getset
*/
#define ePRIV_PROP_DEC_PRIV(t, p, m) ePROP_DEC(t,p); ePRIV_PROP_DEC_##m(t,p)
/**
* private property PUBLIC function DECLARATIONS
* @param c Type
* @param t Function return type
* @param p Property name
* @param m get/set/getset
*/
#define ePRIV_PROP_FUNC_DEC_get(c, t, p) t c##_get_##p(void * eOBJ);
#define ePRIV_PROP_FUNC_DEC_set(c, t, p) void c##_set_##p(void * eOBJ, t v );
#define ePRIV_PROP_FUNC_DEC_getset(c, t, p) ePRIV_PROP_FUNC_DEC_get(c, t, p) ePRIV_PROP_FUNC_DEC_set(c, t, p)
#define ePRIV_PROP_FUNC_DEC(c, t, p, m) ePRIV_PROP_FUNC_DEC_##m(c, t, p)
/**
* private property function ALLOCATIONS for object function pointers in instantiate
* @param c Type
* @param p Property name
* @param v Property value
* @param m get/set/getset
*/
#define ePRIV_PROP_DEF_get(c, p) self->get_##p = &c##_get_##p
#define ePRIV_PROP_DEF_set(c, p) self->set_##p = &c##_set_##p
#define ePRIV_PROP_DEF_getset(c, p) ePRIV_PROP_DEF_get(c, p); ePRIV_PROP_DEF_set(c, p)
#define ePRIV_PROP_DEF(c, p, v, m) self->p = v; ePRIV_PROP_DEF_##m(c, p)
/**
* private property PUBLIC get/set function DEFINITIONS
* @param c Type
* @param t Function return type
* @param p Property name
* @param m get/set/getset
*/
#define ePRIV_PROP_FUNC_DEF_get(c, t, p) t c##_get_##p(void * eOBJ){ eSELF(c); return self->p; }
#define ePRIV_PROP_FUNC_DEF_set(c, t, p) void c##_set_##p(void * eOBJ, t v ){ eSELF(c); self->p = v; }
#define ePRIV_PROP_FUNC_DEF_getset(c, t, p) ePRIV_PROP_FUNC_DEF_get(c, t, p) ePRIV_PROP_FUNC_DEF_set(c, t, p)
#define ePRIV_PROP_FUNC_DEF(c, t, p, m) ePRIV_PROP_FUNC_DEF_##m(c, t, p)
/**
* Cast "self" back into the class type
* @param <classtype_t> c
*/
#define eSELF(c) c * self = (c*)eOBJ
/**
* Get the value of a protected variable 'p' within object 'o'
* Prints to stderr if property is private
* @param var o Object
* @param var p Object property
*/
#define eGET(o, p) o->get_##p(o)
/**
* Set the value of a protected variable 'x' within object 'o'
* Prints to stderr if property is private
* @param var o Object
* @param var p Object property
* @param var v The new value
*/
#define eSET(o, p, v) o->set_##p(o, v)
/**
* Method call wrapper that passes object as first argument for use of eSELF()
* @param var o Object
* @param var m The method
* @param ... Other args
*/
#define eMETH(o, m, ...) P99_IF_EMPTY(__VA_ARGS__) ((*o->m)(o)) ((*o->m)(o, __VA_ARGS__))
/**
* Free memory on heap for object
* @param var o Object variable name
*/
#define eDESTROY(o) free(o); o = ((void*)0)
/**
* Free memory on heap for object by calling defined function to allow further actions
* such as destroying string / struct members within object
* @param <classtype_t> c
* @param var o Object variable name
*/
#define eDESTROY_M(c, o) c##_heap_destruct(o); o = ((void*)0)
#endif //OOP_MAIN_H
With some example files:
#ifndef OOP_CLASS_H
#define OOP_CLASS_H
//interface definitions can contain expressly written variables,
//other e@ macros etc as needed.
#define eINTERFACE_interface()
int poop;
int shmoop
struct parent{
int pprop1;
int pprop2;
};
//PUBLIC DEFINITION AND METHODS
struct Class_t{
//parent for upcasts
eDIR_EXTENDS(parent, parent);
//interface
eIMPLEMENTS(interface);
//define a public property
ePROP_DEC(int, prop1);
//define function pointers for get, set or both for private property
ePRIV_PROP_DEC_PUB(int, prop2, get);
//public method function pointer
int (*method1)(void * eOBJ);
};
//public class function declarations
//get and/or set PUBLIC function declarations
ePRIV_PROP_FUNC_DEC(Class_t, int, prop2, get)
//other defined public methods declarations
int Class_t_method1(void * eOBJ);
//always need an instantiate. Void pointer so function can cast back to struct type pointer
//this means we don't get conflicting type errors
void Class_t_instantiate(void * eOBJ);
#endif //OOP_CLASS_H
//need OOP macros
#include "eOOPc.h"
//include public declaration
#include "class.h"
//PRIVATE DECLARAION
typedef struct{
eDIR_EXTENDS(parent, parent);
//interface
eIMPLEMENTS(interface);
//matching public prop declaration
ePROP_DEC(int, prop1);
//private property declaration + PUBLIC function pointer declarations
ePRIV_PROP_DEC_PRIV(int, prop2, get);
//public method function pointer
int (*method1)(void * eOBJ);
//private method function pointer
int (*method2)(void * eOBJ);
} Class_t;
//PRIVATE METHODS
int Class_t_method2(void * eOBJ){
eSELF(Class_t);
return self->prop1;
}
//PUBLIC METHOD DEFINITIONS TO OVERRIDE DECLARATIONS
//private property PUBLIC get/set method definitions
ePRIV_PROP_FUNC_DEF(Class_t, int, prop2, get)
//other public method definitions
int Class_t_method1(void * eOBJ){
eSELF(Class_t);
return 4;
}
void Class_t_instantiate(void * eOBJ){
eSELF(Class_t);
ePROP_DEF(prop1, 3);
ePRIV_PROP_DEF(Class_t, prop2, 4, get);
self->method1 = &Class_t_method1;
self->method2 = &Class_t_method2;
}
Note it only has access to the class.h file – the further methods and properties defined in class.c are private and inaccessible.
#include "eOOPc.h"
#include "class.h"
int main (void){
//instantiate object (i.e. new)
eNEW(Class_t, object);
int i = eMETH(object, method1);
return 0;
}