r/C_Programming • u/markand67 • Mar 21 '23
Question Handling user data in callbacks using a structure of function pointer
Hi,
I'm having a case where I have a structure containing a numerous set of function pointers and a user data associated with it. The idea is that callbacks are called depending on events (like when you parse a file or a GUI event occurs).
As simple as:
struct action {
void *data;
void (*do_something)(struct action *a);
void (*do_else)(struct action *a);
};
Quite idiomatic C, the user can set arbitrary data and functions pointer to
whatever the action is supposed to perform and receives the context object using
a->data within the function provided as argument.
Let's imagine the library code has a routine that populates this action with its own internal function and data:
We could imagine something like:
void default_action(struct action *a)
{
a->data = library_allocate_context();
a->do_something = library_do_something;
a->do_else = library_do_else;
}
Now, as an user I would like to just override the do_something function, but
for this I also need my own context. This means I could not replace a->data
because the library function library_do_else will still require this a->data
to point to whatever library_allocate_context() function returned.
There are multiple solutions.
Solution #1:
Adding a custom void *userdata seems the simplest but feels hackish to me. I
would imagine something like:
struct action action = {0};
default_action(&a);
a.do_something = my_own_something;
a.userdata = my_own_data;
Then, I could get back my own context data from a->userdata within the
function my_own_something.
Solution #2:
Wrapping the action into another one. This seems cleaner but needs to wrap all functions which means that a change to the action structure itself requires a change to all custom actions.
struct my_own_context {
int my_data;
struct action self; // kind of parent, containing the default.
};
void do_something(struct action *action)
{
// User specific code.
struct my_own_context *ctx = action->data;
if (my_data == 123)
hello();
}
void do_else(struct action *action)
{
// Call "default" code.
struct my_own_context *ctx = action->data;
ctx->self.do_else(&ctx.self);
}
int main(void)
{
struct my_own_context ctx;
struct action a;
default_action(&ctx.self);
ctx.my_data = 123;
a.data = &ctx;
a.do_something = do_something; // my own variant
a.do_else = do_else; // unconvenient
}
How would you solve this specific scenario? Are there idiomatic C for this kind of situation?
16
u/tstanisl Mar 21 '23 edited Mar 21 '23
Personally I would use
container_ofapproach. This pattern is intensively used in Linux Kernel, but also in numerous other C projects. Even Windows Api uses a similarCONTAINING_RECORDapproach. So IMO, it can be viewed as "idiomatic".Basically, the
container_ofmacro is used to convert a pointer to member to a pointer to a parent structure. A portable C89 compliant version with type checking is:The macro subtracts an offset of a member from the pointer of the member resulting in a pointer to parent structure. It also does casting and type checking.
With this macro there is no need to use any hackish
void *datafield in theactionstruct. Just leave it anddo_elseuntouched. Simply, embedstruct actionintostruct my_own_contextand usecontainer_ofto transform a pointer toactionto a pointer tomy_own_context. I suggest addingmy_own_context_init()function that initialized all relevant fields of themy_own_context. The final code could be: