Composition?
typedef struct { /* ... */ } context;
typedef struct {
context ctx;
/* ... */
} foo_context;
typedef struct {
context ctx;
/* ... */
} bar_context;
void some_general_method(context* ctx, int a, int b);
void some_foo_specific_method(foo_context* foo_ctx, int c, int d);
foo_context* foo_ctx;
some_foo_specific_method(foo_ctx, 0, 0);
some_general_method(&foo_ctx->ctx, 1, 1);
For checked downcasts, you could include an enum type tag inside `context` and have something like `foo_context* as_foo_context(context* ctx)` for downcasts, which, if `context` is at the beginning of `foo_context`, could just check the tag and cast the `ctx` pointer to `foo_context*` (or do a little pointer arithmetic if `context` is somewhere else in the containing struct). Return `NULL` (or assert) if the tag doesn't match.