What is OPF
Programming?
Introduction
OPF (O
bject P
ointer F
orwarding) Programming is a programming skill for
low-level languages like C language.
It can also be represented by:
(*p)->f(p)
Before explanations, we would like to show this memory layout graph first:
Variable Object Interface (Function Pointers)
+--------+ .---> +--------+ .---> +--------+
| ---+--' | ---+--' | f1 |
+--------+ +--------+ +--------+
| | | f2 |
| | +--------+
| |
| |
+--------+
This graph has shown how (*p)->f(p)
works.
The aim of this skill, is to provide a Top-Down
programming way,
which is the key to build huge program.
Programs designed in the Bottom-Up
style:
We implement A
, then implement B
who is based on A
.
Programs designed in the Top-Down
style:
We implement B
first, then someone else implement A
to finish our B
.
So:
How can we finish
B
whenB
depends onA
andA
does not exist yet?
OPF
make it possible.
Example
Say we want to write a set of function like draw_rect
, draw_circle
,
which support different printing targets.
How to do that? If we have a generic draw_point
, it would be easy to
implement those function.
So the problem got converted into:
How to implement a generic
draw_point
.
With the Point Five
skill, we write a interface struct like this:
typedef int (*painter_draw_point_fn_t)(void *self, struct point pos, int color);
struct painter_i {
painter_draw_point_fn_t draw_point;
};
Now, here comes the magical part: We can implement the drawing function without any real screen drivers!
int draw_rect(struct painter_i **painter, struct point p1, struct point p2,
int color)
{
struct point c;
/* ... */
for (/* get the next point to draw and store the point in c */) {
/* (*p)->f(p) */
(*painter)->draw_point(painter, c, color);
}
/* ... */
return 0;
}
This is the Top-Down
design.
Now let's implement a RGB screen driver:
struct rgb_screen {
struct painter_i *interface; /* has to be the first field. */
int color_type;
int color_mask;
/* ... */
};
int rgb_screen_draw_point(struct rgb_screen *self, struct point pos, int color)
{
/*
* Draw point on the RGB screen,
* `self->color_type` and `self->color_mask` will be used here.
*/
}
struct pointer_i rgb_screen_interface = {
.draw_point = (painter_draw_point_fn_t)rgb_screen_draw_point,
};
int rgb_screen_init(struct rgb_screen *self)
{
self->interface = &rgb_screen_interface;
}
Then let's implement a mono screen driver:
struct mono_screen {
struct painter_i *interface; /* has to be the first field. */
unsigned char bit_buffer[1024 * 768];
/* ... */
};
int mono_screen_draw_point(struct mono_screen *self, struct point pos,
int color)
{
/*
* Draw point on the mono screen,
* `self->bit_buffer` will be used here.
*/
}
struct pointer_i mono_screen_interface = {
.draw_point = (painter_draw_point_fn_t)mono_screen_draw_point,
};
int mono_screen_init(struct mono_screen *self)
{
self->interface = &mono_screen_interface;
}
Now here is how we use the code:
struct rgb_screen scr1;
struct mono_screen scr2;
draw_rect(&scr1, p1, p2, RED);
draw_rect(&scr2, p1, p2, WHITE);
draw_circle(&scr1, p1, 10, RED);
draw_circle(&scr2, p1, 10, RED);
You may feel OPF
Programming a little bit complex, and don't understand why
those things are done.
But when we get more familiar with this skill, we can construct the whole thing inside our head without effort.
Rules and Styles
The core concept of OPF
do not have coding rules, but a consistent coding rule
can make collaborating easier.
After some practice, some simple rules are collected:
- The 1st parameter should be the object pointers, and should be named
self
. - Interface structs should be named with suffix
_i
. (e.g.struct iter_i
) - Function only return status code, results are passed out though pointers.
- Follow the Linux kernel coding style.
Explanations for the 3th Rule
Many C code return the result directly, and return error code by pointer.
e.g.
int fn(int a, int b, int *error)
{
if (some_check()) {
*error = CODE_xxx;
return SOMETHING;
}
return a + b;
}
But the OPF
Programming use the following style:
int fn(int a, int b, int *result)
{
if (some_check())
return CODE_xxx;
*result = a + b;
return 0;
}
Error code are returned, and result is return through argument pointer. Returning 0 means success.
Some coding styles return booleans, which means
1
means success, this is different fromOPF
Programming.
Explanations for the 4th Rule
There are 2 reasons for choosing Linux kernel coding style:
- It is widely used in the world of C language.
- Its wide indentation force us to write modular code.
Since we use Linux kernel coding style, variables, functions and types are all
named in snake case (like a_b_c
).
Camel case (like aBc
) or Pascal case (like Abc
) is not used.
Iterator (Another example)
The OPF Programming
uses many iterators, the iterator itself is an
application of OPF Programming
, but we pick it out and describe it since
it's widely used.
In OPF Programming
, we only need to implement 2 interfaces:
_iter_init
and _iter_next
.
When we use it, code should look like this:
int blah(void)
{
struct xxx_iter iter;
struct element *tmp;
size_t index;
if (xxx_iter_init(&iter))
return XXX_ITER_INIT_failed;
while (!xxx_iter_next(&iter, &tmp, &index)) {
if (do_something_on_element(tmp))
return XXX_ITER_ELE_ERROR;
}
return 0;
}
Suggestions
The OPF Programming
style is like Boxing in the programming paradigm world.
It contains only a few rules, but we can solve huge problem by combing those
simple rules.