As you may know, C is not a dynamic language by default, the behaviour of this marvelous technology tends to be imperative and sequential, we tend to manage the software’s flow by using callbacks or conditional statements and other techniques like event-driven are not usually used, but, does it means that it’s not possible?
Not at all, C is the best language for a reason, it doesn’t limit you at all, this doesn’t mean it’ll be easy, and, of course, treating with old standards like ANSI C will make it little harder, not spoon-feeding you or being as helpful as C++ could be, but, in the other hand it won’t bloat your program with useless things neither and will let you work as you please.
In this case, we will write an event-driven “framework” for an ANSI C program so we will be able to bind, listen and trigger events as we would with JavaScript.
First of all, we need to understand what an event is.
Events are no more than identifiers stored in a stack. A function wants to do some async work and triggers an event, by doing this, the events system stacks in a pile the event name with the arguments provided.
event-d::argument event-c::argument event-b::argument event-a::argument
Those are events, what now?
We need some handlers to manage those events, those are called “listeners”. Listeners are functions that are bound to an event name so, whenever an event with that identifier is triggered, its bound handler will catch the arguments and perform its defined task.
Listeners are no more than functions bound to an event identifier:
void handlerA(Arguments *args) { printf("Event %s triggered with arguments", args.event); } on_event("event-a", &handlerA); Arguments args; args.event = "event-a"; trigger("event-a", &args)
As you can see in the example above, the handlerA function is bound as listener for “event-a” type events by using the function on_event() and, using the function “trigger”, the same event is raised to the event system, firing the handlerA function and passing to it the arguments defined in the trigger, in this case, the handler will print “Event event-a triggered with arguments” in the console.
Cool, we now know what events and listeners are and how to use them, but the system is not that simple, in order to evaluate those events, we should build an event digester, this is a loop that will pop an event from the stack, read it’s identifier, find a bound handler, and execute it with the given parameters. With that we have our whole events system ready.
What’s written above is just the theory, lets see it in the code… An events system is not as simple as it can seem so, for clarity sakes, I’ll ignore some functions and algorithms, this code must be treated as pseudocode.
Lets code!!
typedef ... Arguments; // Treat this as you please typedef struct { char *name; Arguments *args; } Event; typedef struct { char *name; void (*handler)(void*); } Handler; Handler *listeners; Event *poll; void on_event(char *name, void *handler) { Handler hn; hn.name = name; hn.handler = handler; add_handler_to_listeners(hn); // Resize and append } void trigger(char *name, Arguments *args) { Event ev; ev.name = name; ev.args = args; add_event_to_poll(ev); // Resize and append } void digest() { Event *ev = poll[0]; // pop first in events poll . . . // Manage errors // Find the event handler void *hnd = find_handler_in_listeners(ev -> name); // Execute it with the event arguments hnd(ev -> args); delete poll[0]; // remove the used event from the poll }
Those are the very basics for an event-driven system, now, if we put the digest in a loop (usually in other thread) it’ll fetch events from the poll executing the handlers for each one.
If you’d like to see a working example, don’t hesitate to visit Pandora project in my github, a socket handler library written in C that applies this kind of event-driven behaviour, it could clarify some points.
Hope it helped.