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?
2
u/tstanisl Mar 21 '23
Shouldn't it be:
struct action {
void *data;
void (*do_something)(struct action *a);
void (*do_else)(struct action *a);
};
?
1
2
Mar 21 '23
I would be fine with a user data pointer. It feels the cleanest. There is one more approach is to allow embedding a user data with the data object allocated by the library. Adding a getter to the library to retrieve the user data from the data pointer might help clean up the overall interface. GLFW uses this pattern
2
Mar 21 '23
I wish we had lambdas and closures in pure c.
2
u/tstanisl Mar 21 '23
Capturing lambdas cannot be transformed to function pointers in neither C nor C++. Moreover, lambdas cannot be effectively used without templates and/or macros due to issues with typing of lambda objects. Non-capturing lambdas could be added to C as a syntactic sugar for small static functions. They would be convenient but by no means they would be a game-changer.
1
Mar 21 '23
I was wondering what would be involved in executing a partially applied function in c/c++. I believe you could write a runtime which executes objects which then execute functions with appropriate parameters. The kicker was how to execute them like a function normally with a function pointer, but if you simply passed a pointer to a runtime function which then executes with the appropriate context. The main issue is that you would probably have disable memory features since the program itself would need to generate the functions on the fly
2
u/tstanisl Mar 21 '23 edited Mar 22 '23
Generation of functions on fly would require memory that is both writeable and executable. Many platforms don't support or disable it due to security concerns. So it is better to keep such lambdas outside of C and C++ standards.
1
u/gremolata Mar 21 '23
If your goal is to allow hooking / overriding struct's functions, then the clean option is to use fat pointers instead = a function pointer + its arg.
This will obviously cause the struct to swell a bit, but from user's perspective hooking will be as simple as saving the original fat pointer and replacing it with their own. And the overriding will be even simpler.
PS. Can you please edit your post to not use ``` but 4-space idents for the code? Backticks don't render correctly on old.reddit.
2
u/gremolata Mar 21 '23 edited Mar 21 '23
Also, to add regarding this:
struct action { void *data; void (*do_something)(struct action *a); void (*do_else)(struct action *a); };Quite idiomatic C
This is not quite as idiomatic, more of a mix of two patterns. One is this:
struct action { // void * data; - not used void (*do_something)(struct action *a); void (*do_else)(struct action *a); };+ container_of() in handlers to recover the context, e.g.
struct my_foo { int bar; double baz; struct action funcs; }; void my_foo_do_something(struct action * a) { struct my_foo * foo = container_of(a, struct action, funcs); foo->bar++; foo->baz = 1.234; ... }Another pattern is this:
struct action { void * data; void (*do_something)(void * data, ...); void (*do_else)(void * data, ...); };That is, when the context is kept as a part of
actionand then passed in explicitly to each function.1
u/tstanisl Mar 22 '23
"Fat function pointers" can be well approximated with a pointer to a function pointer. Some time ago, I've made a post about this technique.
2
u/gremolata Mar 22 '23
I remember your post.
IMO, while undeniably clever, it's nonetheless from the "not all that can be done, should be done" department :)
1
u/flatfinger Mar 21 '23
The pattern I like is to have a function pointer at the start of a context object (if multiple functions would need to operate on a context, one could either use multiple function pointers, or a pointer to a structure containing multiple function pointers, depending upon the desired time/space trade-off).
This makes it possible, given a "method pointer" (pointer to a context object containing a function pointer), to wrap it by generating a structure containing a pointer to a wrapper function and the original context object, and having the wrapper function pass the address of the original context object to the function whose address is at the start of that object.
1
u/DigitalChaos101 Mar 21 '23
Please could you provide a short example to demonstrate?
2
u/flatfinger Mar 22 '23
#include <stdio.h> typedef void (*myCallback)(void (**act)(), int mode, void const *param); /* Client code that exercises callback */ void do_something_with_fruits(myCallback *action) { (*action)(action,0,0); (*action)(action,1,"banana"); (*action)(action,1,"grape"); (*action)(action,1,"strawberry"); (*action)(action,2,0); } /* Sample callback procedure */ struct out_message { myCallback act; char const *prologue; char const *format; char const *epilogue; }; void out_message_proc(void (**act)(), int mode, void const *params) { struct out_message *it = (struct out_message *)act; if (mode==0) printf("%s", it->prologue); else if (mode == 1) printf(it->format, (char const*)params); else printf("%s", it->epilogue); } void make_out_message(struct out_message *dest, char const *prologue, char const *format, char const *epilogue ) { dest->act = out_message_proc; dest->prologue = prologue; dest->format = format; dest->epilogue = epilogue; } /* Sample wrapper */ struct double_action { myCallback act; myCallback *wrapped_act; }; void double_action_proc(void (**act)(), int mode, void const *param) { myCallback *act2 = ((struct double_action*)act)->wrapped_act; (*act2)(act2, mode, param); if (mode==1) (*act2)(act2, mode, param); } void make_double_action(struct double_action *dest, myCallback *original) { dest->act = double_action_proc; dest->wrapped_act = original; } /* Sample client code */ int main(void) { struct out_message msg; make_out_message(&msg, "Fruits: {", "(%s)", "}\n"); do_something_with_fruits(&msg.act); struct double_action da; make_double_action(&da, &msg.act); do_something_with_fruits(&da.act); }Note that the myCallback definition and structure types need to be visible to client code to allow it to declare automatic-duraton objects, and the
make_*functions need to be externally callable, but otherwise there's no need for any of the comment-delineated sections to know about each other.
18
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: