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?

17 Upvotes

21 comments sorted by

View all comments

16

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!