0

I created a small library, which takes a callback function:

MyClass::add_callback(bool (*callback_function)(), byte behaviour) {
  // ...
  bool result = callback_function();
  // ...
}

So far, I'm using it for periodic polling (for which I don't want to use ISR interrupts). However, I like to use it for other periodic calls that require a state. A simple example is a blinking light, which has an on-off state, as well as knowledge when the next state change needs to occur.

I like to change this method or add another method that takes a method, instead of a function. How should I do this? I'm struggling with the C basics, but answers on StackOverflow seem foccused to more modern C variants. For example, this great answer on Stackoverflow recommends to use boost::bind, but I rather not include such a large dependency.

What I'm looking for is something akin to:

MyClass::add_callback(void *obj, bool (*callback_method)(), byte behaviour) {
  // ...
  bool result = Object::callback_method(&obj);
  // ...
}

where Object::callback_method(&obj); should be equivalent to Object obj; bool result = obj.callback_method();.

For bonus points, is it possible to make this independent on the Object class?

I'm happy for pointers to other sites (like StackOverflow), provided it is applicable to a bare Arduino program.

MacFreek
  • 103
  • 5

1 Answers1

3

The easiest method I know to do this would be to use a "C"-style wrapper for the member function call. This is in fact done to interface C callbacks with C++ objects.

You could do it like this:

struct Blinky {
  //state etc.
  bool isOn() { return /* something useful */ true; }
};

// The "C" wrapper for the callback bool Blinky_callback(void vblinky) { return static_cast<Blinky>(vblinky)->isOn(); }

struct MyClass { void add_callback(void obj, bool (callback_method)(void *), byte behaviour) { // ... bool result = callback_method(obj); // ... } };

// Globals MyClass c; Blinky b;

void setup() { c.add_callback(&b, Blinky_callback, 0); }

A slightly tidier way with a static member (functionally identical):

struct Blinky {
  //state etc.
  bool isOn() { return /* something useful */ true; }

static bool callback(void vblinky) { return static_cast<Blinky>(vblinky)->isOn(); } };

// ... c.add_callback(&b, Blinky::callback, 0);

Other variants would be using inheritance and virtual functions.

// Abstract base class
struct Sensor {
  virtual bool do_check() = 0;
};

struct Blinky : public Sensor { bool do_check() override { return true; } }

Then you can have your add_callback look like:

void MyClass::add_sensor(Sensor *sensor) {
  // ...
  bool result = sensor->do_check();
  // ...
}

This is relatively nice to code with, and the compiler has a fighting chance at keeping your code typesafe, if all the "things" you want to control can conform to the same interface. The virtual dispatch won't cost much more (if anything) than the static wrapper in the previous example. You "pay" a few bytes per class and one pointer per instance for the virtual table though.

More modern techniques would involve templates and/or lambdas, or perhaps std::function, but all that seems overkill if your need is as described (and might generate a lot more code than you're expecting, or do dynamic allocations for std::function which is best avoided in context).


Remark: if you want to do it with (non-static) member function pointers, it is possible but the syntax is hairy:

void add_callback(Blinky *obj, bool (Blinky::*callback_method)(), byte behaviour) {
  // ...
  bool result = (obj->*callback_method)();
  // ...
}

And you're tied to that class. Unless you make add_callback a template, but beware extra codegen.

Mat
  • 464
  • 1
  • 4
  • 8