Callback Functions

Prerequisite: - You should know what function pointers are and how they work.

--------------------------------------------------------------------------------------------------------------------------

The use of callback functions is very diverse, and can be found in many software
applications. Due to this, it is important to understand what a callback function is,
how it works, and in what circumstances could they be used.

What are they?
- Put simply, a callback function is essentially a function that is used as a parameter in another function through the use of a function pointer. The callback is occurring when the pointer calls the function it is pointing too.


What can we do with them?

- The purpose of callbacks is for the quick and efficient use of functions within other function. Because you have a direct pointer to the callback function's address, you have immediate access to it. This makes callbacks versatile and re-usable. Lets take a look at some examples of the use of callbacks in our projects:

Ex 1) Status code:

#include "status.h"

#include <stdlib.h>

static Status s_global_status = {
  .source = "",   //
  .caller = "",   //
  .message = "",  //
};
static status_callback s_callback;

StatusCode status_impl_update(const StatusCode code, const char* source, const char* caller,
                              const char* message) {
  s_global_status.code = code;
  s_global_status.source = source;
  s_global_status.caller = caller;
  s_global_status.message = message;
  if (s_callback != NULL) {
    s_callback(&s_global_status);
  }
  return code;
}

Status status_get(void) {
  return s_global_status;
}

void status_register_callback(status_callback callback) {
  s_callback = callback;
}


This first example is in the status source code, responsible for specifying current status as well as returning specific types of status errors. The callback function in this case is status_callback (you can find this pointer function declaration in the header file Status.h). In this case, this function is used for getting the current status of the object in question along with all of the relevant information that one might want from a status code feedback (if there is a status error, what type of error is it? Where is the error coming from?).


Ex 2) Finite State Machine Code:

#include <stdbool.h>
#include <stdint.h>

#include "event_queue.h"
#include "fsm_impl.h"

// Forward-declares the state for use.
#define FSM_DECLARE_STATE(state) _FSM_DECLARE_STATE(state)
// Defines the state transition table.
#define FSM_STATE_TRANSITION(state) _FSM_STATE_TRANSITION(state)
// Adds an entry to the state transition table.
#define FSM_ADD_TRANSITION(event_id, state) _FSM_ADD_TRANSITION(event_id, state)
// Adds an entry with a conditional boolean guard to the state transition table.
#define FSM_ADD_GUARDED_TRANSITION(event_id, guard, state) \
  _FSM_ADD_GUARDED_TRANSITION(event_id, guard, state)

// Initializes an FSM state with an output function.
#define fsm_state_init(state, output_func) _fsm_state_init(state, output_func)

struct FSM;
typedef void (*StateOutput)(struct FSM *fsm, const Event *e, void *context);
typedef void (*StateTransition)(struct FSM *fsm, const Event *e, bool *transitioned);
typedef bool (*StateTransitionGuard)(const struct FSM *fsm, const Event *e, void *context);

typedef struct State {
  const char *name;
  StateOutput output;
  StateTransition table;
} State;

typedef struct FSM {
  const char *name;
  State *last_state;
  State *current_state;
  void *context;
} FSM;

// Initializes the FSM.
void fsm_init(FSM *fsm, const char *name, State *default_state, void *context);

// Returns whether a transition occurred in the FSM.
bool fsm_process_event(FSM *fsm, const Event *e);

bool fsm_guard_true(FSM *fsm, const Event *e, void *context);


This second example is in our finite state machine code. In this case, our callback function is context. You can find context's declaration in some of the FSM source files (ex: horn_fsm.c), and you will notice it is in fact a function pointer. In this case, context acts as an arbitrary void pointer that we can use for whatever extra function we might want to pass. Due to this, the context pointer becomes a very versatile function pointer that can take on multiple responsibilities - this becomes evident when you look at other files that actually use context (ex: gpio_it.c     /     soft_timer.c      /     horn_fsm.c).


Summary:

  • What are callback functions
  • What is their purpose
  • Examples of use of callback functions in our code