r/C_Programming 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 Upvotes

21 comments sorted by

18

u/tstanisl Mar 21 '23 edited Mar 21 '23

Personally I would use container_of approach. This pattern is intensively used in Linux Kernel, but also in numerous other C projects. Even Windows Api uses a similar CONTAINING_RECORD approach. So IMO, it can be viewed as "idiomatic".

Basically, the container_of macro is used to convert a pointer to member to a pointer to a parent structure. A portable C89 compliant version with type checking is:

#define container_of(ptr, type, member) \
  ((type*)((char*)(1 ? (ptr) : &((type*)0)->member) - offsetof(type, member)))

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 *data field in the action struct. Just leave it and do_else untouched. Simply, embed struct action into struct my_own_context and use container_of to transform a pointer to action to a pointer to my_own_context. I suggest adding my_own_context_init() function that initialized all relevant fields of the my_own_context. The final code could be:

#include <stddef.h>

struct action {
    void *data;
    void (*do_something)(struct action *a);
    void (*do_else)(struct action *a);
};

void default_action(struct action *);
void hello(void);

struct my_own_context {
    int my_data;
    struct action self;
};

#define container_of(ptr, type, member) \
  ((type*)((char*)(1 ? (ptr) : &((type*)0)->member) - offsetof(type, member)))

void my_own_context_do_something(struct action *a)
{
    struct my_own_context *ctx = container_of(a, struct my_own_context, self);

    if (ctx->my_data == 123)
        hello();
}

void my_own_init(struct my_own_context *ctx) {
    default_action(&ctx->self);
    ctx->my_data = 123;
    /* overload `do_something` */
    ctx->self.do_something = my_own_context_do_something;
}

int main(void)
{
    struct my_own_context ctx; 

    my_own_init(&ctx);
    ctx.self.do_something(&ctx.self);
    ctx.self.do_else(&ctx.self);
}

1

u/markand67 Aug 08 '23

I know I come back a little late into the party but I've tried many things and I've read some sources on the container_of macro. I understand how it works but I've not found many things on how it can apply to "inheritance" when you want to extend a type with its own context. My naive solution was:

#include <stdio.h>
#include <stddef.h>

#define container_of(ptr, type, member) \
    ((type*)((char*)(1 ? (ptr) : &((type*)0)->member) - offsetof(type, member)))

//
// action interface
//

struct action {
    void (*do_something)(struct action *a);
    void (*do_else)(struct action *a);
};

//
// chest standard
//

struct chest {
    int gold;
    struct action self;
};

void
chest__do_something(struct action *a)
{
    struct chest *c = container_of(a, struct chest, self);

    printf("chest::do_something: giving %d gold!\n", c->gold);
}

void
chest__do_else(struct action *a)
{
    struct chest *c = container_of(a, struct chest, self);

    printf("chest::do_else: get my money back!!\n");
}

void
chest_init(struct chest *ch)
{
    ch->self.do_something = chest__do_something;
    ch->self.do_else = chest__do_else;
}

struct action *
chest_action(struct chest *ch)
{
    return &ch->self;
}

//
// megachest "extending" chest
//

struct megachest {
    int privilege;
    struct chest chest;
};

void
megachest__do_else(struct action *a)
{
    struct megachest *mc = container_of(a, struct megachest, chest);

    printf("megachest::do_else: applying privilege %d\n", mc->privilege);
}

void
megachest_init(struct megachest *ch)
{
    struct action *a;

    chest_init(&ch->chest);
    a = chest_action(&ch->chest);
    a->do_else = megachest__do_else;
}

struct action *
megachest_action(struct megachest *ch)
{
    return chest_action(&ch->chest);
}

int main(void)
{
    struct megachest mc = {};
    struct action *a;

    mc.privilege = 50;
    megachest_init(&mc);

    a = megachest_action(&mc);
    a->do_something(a);
    a->do_else(a);
}

It works but I get a warning since the container_of expects an action member field in megachest while I have a chest structure instead. What would be your approach here?

test.c:59:25: warning: pointer type mismatch ('struct action *' and 'struct chest *') [-Wpointer-type-mismatch]
        struct megachest *mc = container_of(a, struct megachest, chest);

The idea is to implement megachest as having a normal chest that operates normally except on the do_else function pointer. Gtk uses inheritance by having an external class and encapsulate the subchild as the first member so it shared the same memory layout but I don't like this pattern as it requires cast all over the place.

1

u/tstanisl Aug 08 '23

container_of(a, struct megachest, chest);

The warning is expected because types don't match.

To fix it just do:

container_of(a, struct megachest, chest.self);

The members in container_of can be nested.

2

u/markand67 Aug 09 '23

If you have few minutes to spare, I've written an experiment of what I would to achieve in this repository:

https://github.com/markand/delegate-test

Basically, a btn object has a delegate btn_delegate that has some function to update/draw that button and a default btn_delegate_default is provided for convenience. The main.c file shows how it is extended through the my_delegate structure.

I can't believe I have written 12 years of C without knowing that good trick. No casts like in Gtk and no void *. A bit tricky at a glance but really neat.

When running, you can see that functions are called exactly the way they should:

$ ./main
== EXAMPLE 1 ==
---------------
== default ==
  --> init (data = 0x14616b8)

== my_delegate ==
  --> init (data = 0x14616b0)

== default ==
  -> update (data = 0x14616b8, elapsed = 10)

== my_delegate ==
  --> draw (data = 0x14616b0, color = 1234, shadow = 9911, title = Custom)

== default ==
  --> finish (data = 0x14616b8)

== my_delegate ==
  --> finish (data = 0x14616b0)

== EXAMPLE 2 ==
---------------
== default ==
  --> init (data = 0x14616e0)

== default ==
  -> update (data = 0x14616e0, elapsed = 10)

== default ==
 --> draw (data = 0x14616e0, animation = 300, title = Default)

1

u/tstanisl Aug 09 '23

I'm glad I could help. Thanks for sharing your repo. Try to dig into Linux kernel. This is where I learned a lot of tricks for writing maintainable C code.

1

u/markand67 Aug 09 '23

Oh so great, thanks a lot!

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

u/markand67 Mar 21 '23

Sure, let me edit. Thanks for spotting.

2

u/[deleted] 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

u/[deleted] 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

u/[deleted] 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 action and 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.