instantaphex
instantaphex

Reputation: 1001

C Macro for type safe callbacks

I'm trying to create a very simple event system in c. My interface looks like this:

typedef struct EventEmitter EventEmitter;

EventEmitter* emitter_create();
void emitter_subscribe(EventEmitter* emitter, void (*cb)(void*));
void emitter_publish(EventEmitter* emitter, void *payload);

Everything works correctly, but in order to register an event listener, I need to provide a function pointer that takes a void *.

static void registerOnKeyDown(void (*cb)(void*)) {
  emitter_subscribe(keyDownEmitter, cb);
}

static void registerOnKeyUp(void (*cb)(void*)) {
  emitter_subscribe(keyUpEmitter, cb);
}

Is there any way, using a macro or otherwise, to allow users of EventEmitters to provide a typed callback? Something like:

void onKey(int keyCode) {
  printf("%d", keyCode);
}

instead of:

void onKey(void *keyCode) {
  int code = (int)keyCode;
  printf("%d", code);
}

Upvotes: 1

Views: 1090

Answers (3)

Mark Benningfield
Mark Benningfield

Reputation: 2892

See this answer on Software Engineering SE.

Given your API:

typedef struct EventEmitter EventEmitter;

EventEmitter* emitter_create();
void emitter_subscribe(EventEmitter* emitter, void (*cb)(void*));
void emitter_publish(EventEmitter* emitter, void *payload);

if you modify it to define the subscription macro on that API instead of putting off on the client code, like this:

typedef struct EventEmitter EventEmitter;

EventEmitter* emitter_create();
void emitter_subscribe_impl(EventEmitter* emitter, void (*cb)(void*));
#define emitter_subscribe(emitter, xFunc) emitter_subscribe_impl((emitter), (void(*)(void*))(xFunc))

void emitter_publish_impl(EventEmitter* emitter, void *payload);
#define emitter_publish(emitter, xFunc) emitter_publish_impl((emitter), (void(*)(void*)(xFunc))

Then subscribers can call it with the type that they have in hand. As with all API macros, make sure you document the expected arguements completely, so consumers know what to provide and what to expect.

Upvotes: 0

If you know what your types are, you can use C11 generic selection to find out the type of the argument, and provide it as an enum value.

#include <stdio.h>

typedef struct EventEmitter EventEmitter;
typedef void (*INT_CALLBACK)(int);
typedef void (*VOIDP_CALLBACK)(void *);

enum cbtype {
    _INT_CB,
    _VOIDP_CB
};

void _safe_emitter_subscribe(EventEmitter *emitter,
                             void (*callback)(),
                             enum cbtype type)
{
    printf("Registering a callback of type %d\n", type);
}

#define safe_emitter_subscribe(emitter, callback) \
    _safe_emitter_subscribe(                      \
        emitter,                                  \
        (void (*)())callback,                     \
        _Generic(callback,                        \
             INT_CALLBACK: _INT_CB,               \
             VOIDP_CALLBACK: _VOIDP_CB))

void func1(int a) {
}

void func2(void *a) {
}

int main(void) {
    safe_emitter_subscribe(NULL, func1);
    safe_emitter_subscribe(NULL, func2);
}

Then from the enum value you will know how you'd need to cast the function again: If it is _INT_CB it must be recast as INT_CALLBACK before calling; _VOIDP_CB as VOIDP_CALLBACK and so on.

Upvotes: 1

instantaphex
instantaphex

Reputation: 1001

I ended up solving this by simply casting to and from void (*cb)(void *) as needed in wrapper functions:

typedef void (*keyCallback)(int);
typedef void (*emitterCallback)(void*);

static void registerOnKeyDown(keyCallback cb) {
  emitter_subscribe(keyDownEmitter, (emitterCallback)cb);
}

Upvotes: 1

Related Questions