You don't need macros. You either call via the vtable, or you write a wrapper per interface per message.
E.g.
struct class {
void (*vtable[NUM_SLOTS]);
};
struct object {
struct class * class;
};
typedef struct object object;
const int STRING_LENGTH_SLOT = 0;
int string_length(void * self) {
return ((int (*)(void *))((object *)self)->class->vtable[STRING_LENGTH_SLOT])(self);
}
(EDIT: I forgot just how much gross casting is needed, and added examples of class and object)Such wrappers are fairly trivial to generate if you don't want to handwrite them.
If you want complex inheritance patterns, you might benefit from one more level of indirection, see e.g. Protocol Extension: A Technique for Structuring Large Extensible Software-Systems (M. Franz, 1995). That paper is for Oberon, but it's easily translatable to C - did that way back.
Upside is it let's you group functionality in interfaces with less of an explosion in vtable size, downside is the cost of one extra pointer indirection.