Callback interface
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).