Callback interface

3 minute read

Many objects in the Lely core libraries allow the user to register callback functions. The function signature and registration methods all follow the pattern shown here.

Example of a public C header (lely/lib/obj.h):

#ifndef LELY_LIB_OBJ_H_
#define LELY_LIB_OBJ_H_

typedef struct obj obj_t;

#ifdef __cplusplus
// Ensure that callback functions also use the C calling convention.
extern "C" {
#endif

// The last argument of a callback function is always a pointer to
// user-specified data.
typedef void callback_func_t(Args... args, void *data);

// Retrieves a pointer to the callback function and the user-specified data.
void obj_get_func(const obj_t *obj, callback_func_t **pfunc, void **pdata);

// Sets a callback function. The data pointer is passed as the last argument in
// each invocation of func.
void obj_set_func(obj_t *obj, callback_func_t *func, void *data);

#ifdef __cplusplus
}
#endif

#endif // !LELY_LIB_OBJ_H_

Example of the C implementation (obj.c):

#include <lely/lib/obj.h>

#include <assert.h>

struct obj {
	...
	callback_func_t *callback_func;
	void *callback_data;
	...
};

// An internal helper function that invokes the callback if it is set.
static void obj_callback(obj_t *obj, Args... args);

struct obj *
obj_init(struct obj *obj, Args... args)
{
	...

	obj->callback_func = NULL;
	obj->callback_data = NULL;

	...
}

void
obj_get_func(const obj_t *obj, callback_func_t **pfunc, void **pdata)
{
	assert(obj);

	if (pfunc)
		*pfunc = obj->callback_func;
	if (pdata)
		*pdata = obj->callback_data;
}

void
obj_set_func(obj_t *obj, callback_func_t *func, void *data)
{
	assert(obj);

	obj->callback_func = func;
	obj->callback_data = data;
}

static void
obj_callback(obj_t *obj, Args... args)
{
	assert(obj);

	if (obj->callback_func)
		obj->callback_func(args..., obj->callback_data);
}

The user-specified data pointer can be used to allow the registration of C++ function objects or member functions.

Example of a public C++ header (lely/lib/obj.hpp):

#ifndef LELY_LIB_OBJ_HPP_
#define LELY_LIB_OBJ_HPP_

#include <lely/lib/obj.h>

namespace lely {
namespace lib {

class Object {
 public:
  ...

  void
  get_func(callback_func_t** pfunc, void** pdata = nullptr) const noexcept {
    obj_get_func(*this, pfunc, pdata);
  }

  // Registers a C-style callback function. The user specifies the data pointer.
  void
  set_func(callback_func_t* func, void* data) noexcept {
    obj_set_func(*this, func, data);
  }

  // Registers a function object as the callback. The data pointer is used to
  // store the address of the function object and is therefore not available to
  // the user.
  template <class F>
  void
  set_func(F* f) noexcept {
    set_func(
        [](Args... args, void* data) noexcept {
          auto f = static_cast<F*>(data);
          (*f)(args...);
        },
        static_cast<void*>(f));
  }

  // Registers a member function as the callback. The first template  parameter
  // is the class containing the member function, the second is the address of
  // the member function. The data pointer is used to store the address of the
  // class instance and is therefore not available to the user.
  template <class C, void (C::*M)(Args...)>
  void
  set_func(C* obj) noexcept {
    set_func(
        [](Args... args, void* data) noexcept {
          auto obj = static_cast<C*>(data);
          (obj->*M)(args...);
        },
        static_cast<void*>(obj));
  }

  ...
};

}  // namespace lib
}  // namespace lely

#endif  // !LELY_LIB_OBJ_HPP_

Registering a C-style global (or static) function in C++ is similar to C:

void my_func(Args... args, void* data) noexcept;

void
set_func(Object* obj, void* data) {
  obj->set_func(&my_func, data);
}

The second form of set_func() allows registering a function object. The user is responsible for lifetime of the object.

struct MyFunctionObject {
  void operator()(Args... args) noexcept;
};

void
set_func(Object* obj, MyFunctionObject* f) {
  obj->set_func(f);
}

The third form of set_func() allows member functions to be registered as callbacks. Again, the user is responsible for the lifetime of the instance of the class containing the member function.

class MyClass {
 public:
  void my_func(Args... args) noexcept;
};

void
set_func(Object* obj, MyClass* cls) {
  obj->set_func<MyClass, &MyClass::my_func>(cls);
}

It is also possible to register private methods as callbacks, but only from a function which has private access (i.e., a friend or another method).

Updated: