Search code examples
ccallback

Convert callback/event-based C API to "non-callback" API


I'm trying to build a bi-directional conversion between a binary format and JSON in C (to make the mapping scriptable). I looked at 13 C JSON parsers, and only found 2 that had all the features I wanted. One is LGPL, and the other one uses callbacks. Having to put my code into a separate DLL, so I don't mix LGPL with proprietary code, is a complicated, so I would try the "callback API" first.

The callback-based API looks about like this (heavily edited code):

typedef void *(*api_malloc_func)(void *ctx, size_t sz);
/* ... */
typedef struct {
    api_malloc_func malloc;
    /* ... */
} api_config_params;

typedef struct {
    int (* api_on_bool)(void * ctx, int boolVal);
    int (* api_on_int)(void * ctx, long long intVal);
    /* ...*/
    /* "user-defined data", passed to callbacks. */
    void * ctx;
} api_callbacks;

api_handle api_config(const api_callbacks * callbacks, const api_config_params * cfg);

api_status api_parse(api_handle hand, const unsigned char * json, size_t len);

But what I want/need, to make this API "look like" the binary API, is something like this:

typedef struct {
    api_malloc_func malloc;
    /* ... */
    /* "user-defined data" */
    void * ctx;
} api_config_params;

api_handle api_config(const api_config_params * cfg);

typedef enum api_type_t {
    api_type_bool,
    api_type_int,
    /* ... */
} api_type_t;

api_type_t api_peek_type(api_config_params* reader);

bool api_read_bool(api_config_params* reader, bool* boolVal);

bool api_read_int(api_config_params* reader, long long* intVal);

/* ... */

It's been forever since I last coded in C/C++ (I'm a "Java Programmer" now), so I can't figure out how I can wrap the callback/event-based API, to make it "non-callback/event-based" aka "synchronous".

So, how would I do that? Ideally, without making the code non-thread-safe. Is that even possible?

NOTE: I guess I could make the binary API use callbacks instead, but that would make it slower, and I only care about the speed of the binary API, not the speed of the JSON API.


Solution

  • So, how would I do that? Ideally, without making the code non-thread-safe. Is that even possible?

    With a callback-based parser, there is little room to layer a pull-based interface directly on top. The control flow is committed to reading the input and dispatching events. Execution of a callback can take arbitrarily long and perform arbitrary work, but you can't run a pull interface there because the parser doesn't get to the next item until the callback returns.

    You can, however, run a pull-based API in a second thread. Make the callback functions executed by the low-level push parser enqueue events for the other thread to consume. If necessary, use a blocking queue with a fixed capacity, so that the low-level parser cannot get more than a fixed number of events ahead of the high-level pull parser. The needed synchronization will impose some overhead, but it's unclear how significant that would be for you. There is no particular reason why that could not be made thread safe, but that involves more considerations than just the interactions already described.