Multiple Interfaces And OPF Programming
What does this "interface" mean?
We are talking about implementing something like the interface
in Go language
, or the trait object
in Rust language
.
Does OPF Programming support multiple interface?
The skill part of OPF Programming
, (*p)->f(p)
, do not support multiple interface.
Because it depends on:
interface pointer have to be the first field of the owner struct.
But can we implement a multiple interface mechanism that is compatible with the OPF
Programming?
Yes, we can. And this article describes how we implement it.
Single interface (OPF Programming, for comparison)
Let's define 2 interface: bird_i
which contains fly
method, and dog_i
which contains run
and bark
method.
typedef int (*bird_fly_fn_t)(void *self, int distance);
typedef int (*dog_run_fn_t)(void *self, int distance);
typedef int (*dog_bark_fn_t)(void *self, int count);
struct bird_i {
bird_fly_fn_t fly;
};
struct dog_i {
dog_run_fn_t run;
dog_bark_fn_t bark;
};
Let's see the OPF Programming
implementation (single interface) first.
We define a normal dog species (husky) to implement the dog_i
interface:
struct husky {
struct dog_i *interface;
const char *name;
};
We define a global object husky_interface
which got shared by all struct husky
objects.
struct dog_i husky_interface = {
.run = (dog_run_fn_t)husky_run,
.bark = (dog_bark_fn_t)husky_bark,
};
The the implementation of interfaces:
int husky_run(struct husky *self, int distance) {
my_printf("Husky %s is running, target distance: %d\n", self->name, distance);
return 0;
}
int husky_bark(struct husky *self, int distance) {
my_printf("Husky %s is barking, target count: %d\n", self->name, distance);
return 0;
}
Now, let's define a normal bird species (crow) to implement the bird_i
interface.
struct crow {
struct bird_i *interface;
const char *name;
};
We define a global object crow_interface
which got shared by all struct crow
objects.
struct bird_i crow_interface = {
.fly = (bird_fly_fn_t)crow_fly,
};
Then, the implementation of interfaces:
int crow_fly(struct crow *self, int distance) {
my_printf("Crow %s is flying, target distance: %d\n", self->name, distance);
return 0;
}
Here is how we use it:
struct husky hus;
dog_run_simple((struct dog_i **)&hus, 10);
struct crow black;
bird_fly_simple((struct bird_i **)&black, 1000);
The dog_run_simple
and bird_fly_simple
are just wrappers for (*p)->f(p)
.
int dog_run_simple(struct dog_i **dog, int distance) {
return (*dog)->run(dog, distance);
}
int bird_fly_simple(struct bird_i **bird, int distance) {
return (*bird)->fly(bird, distance);
}
The Multiple-interface mechanism (which is compatible with OPF
)
In short, the core part of this implementation is a singly linked list who chains all interface objects.
There is a Interface Tag
inside every node of the list.
Some offsets are handled to make this mechanism compatible with OPF
Programming.
+--------+ +--------+
| ---+--. | ---+--.
+--------+ | +--------+ |
VARIABLE OBJECT | DOG | | | BIRD | |
+--------+ .---> +--------+ .---> +--------+ `---> +--------+ `--->
| ---+--' | ---+--' | run | | fly |
+--------+ +--------+ +--------+ +--------+
| | | bark |
| | +--------+
| |
| |
+--------+
Now let's implement it. First, we define some structs and macros:
struct complex_i_header {
void *next;
void *tag;
};
#define H_ADDR_TO_I_ADDR(h) (void *)((uintptr_t)h + sizeof(struct complex_i_header))
#define I_ADDR_TO_H_ADDR(i) (struct complex_i_header *)((uintptr_t)i - sizeof(struct complex_i_header))
#define COMPLEX_INTERFACE(TYPE) \
struct { \
struct complex_i_header h; \
TYPE i; \
}
Then we define a function to traverse the list and adjust the offsets.
static int find_i_from_chain(void *interface_chain, void *tag, void **result) {
struct complex_i_header *h;
h = I_ADDR_TO_H_ADDR(interface_chain);
if (h->tag == tag) {
*result = interface_chain;
return 0;
}
if (h->next == NULL)
return 1;
return find_i_from_chain(h->next, tag, result);
}
Now, let's define a special dog species (angle dog), who implements bird_i
and dog_i
in the same time.
struct angel_dog {
void *interface;
int wing_span;
const char *name;
};
With the helper macro, we define 2 global interface object angel_dog_interface_1
and angel_dog_interface_2
,
which got shared by all struct angel_dog
objects.
COMPLEX_INTERFACE(struct bird_i)
angel_dog_interface_1 = {
.h.next = NULL,
.h.tag = &bird_i_anchor,
.i.fly = (bird_fly_fn_t)angel_dog_fly,
};
COMPLEX_INTERFACE(struct dog_i)
angel_dog_interface_2 = {
.h.next = H_ADDR_TO_I_ADDR(&angel_dog_interface_1),
.h.tag = &dog_i_anchor,
.i.run = (dog_run_fn_t)angel_dog_run,
.i.bark = (dog_bark_fn_t)angel_dog_bark,
};
Now here is the implementation of interfaces:
int angel_dog_fly(struct angel_dog *self, int distance) {
my_printf("Angel dog %s is flying, target distance: %d\n", self->name, distance);
return 0;
}
int angel_dog_run(struct angel_dog *self, int distance) {
my_printf("Angel dog %s is running, target distance: %d\n", self->name, distance);
return 0;
}
int angel_dog_bark(struct angel_dog *self, int distance) {
my_printf("Angel dog %s is barking, target count: %d\n", self->name, distance);
return 0;
}
Let's define 2 demo functions to use the multiple-interface objects:
int bird_fly_complex(void *bird, int distance) {
struct bird_i *i;
if (find_i_from_chain(*(void **)bird, &bird_i_anchor, (void **)&i))
return 1;
return i->fly(bird, distance);
}
int dog_run_complex(void *dog, int distance) {
struct dog_i *i;
if (find_i_from_chain(*(void **)dog, &dog_i_anchor, (void **)&i))
return 1;
return i->run(dog, distance);
}
Usage example:
struct angel_dog air;
//...
dog_run_complex(&air, 100);
bird_fly_complex(&air, 2000);
The first interface in the chain is struct dog_i
, so you can use OPF Programming on it (the compatibility):
dog_run_simple((struct dog_i **)&air, 10);
But ...
It's not suggested to use this skill in serious programs. This will be explained in the future.