C宏用于类型安全回调

问题描述 投票:1回答:3

我正试图在c中创建一个非常简单的事件系统。我的界面如下所示:

typedef struct EventEmitter EventEmitter;

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

一切正常,但为了注册一个事件监听器,我需要提供一个带有void *的函数指针。

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

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

有没有办法,使用宏或其他方式,允许EventEmitters的用户提供类型回调?就像是:

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

代替:

void onKey(void *keyCode) {
  int code = (int)keyCode;
  printf("%d", code);
}
c macros
3个回答
0
投票

answer上看到这个Software Engineering SE

鉴于您的API:

typedef struct EventEmitter EventEmitter;

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

如果您修改它以在该API上定义订阅宏而不是推迟客户端代码,如下所示:

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))

然后订阅者可以使用他们手头的类型来调用它。与所有API宏一样,请确保完整记录预期的争论,以便消费者知道要提供什么以及期望什么。


1
投票

我最终通过在包装函数中根据需要简单地转换为void(* cb)(void *)来解决这个问题:

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

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

1
投票

如果您知道类型是什么,则可以使用C11泛型选择来查找参数的类型,并将其作为枚举值提供。

#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);
}

然后从枚举值中你将知道你需要再次强制转换函数:如果它是_INT_CB,它必须在调用之前重铸为INT_CALLBACK; _VOIDP_CBVOIDP_CALLBACK等。

© www.soinside.com 2019 - 2024. All rights reserved.