use library with callback function in other class based code

Hello Community,

for my current project i had the idea to separate my code into different modules.
(for better readability and simpler later additions)
my idea was to do one class per module and to combine the modules in the main ino file.
(some great information about what is possible is written in this answer from Nick Gammon on StackExchange Arduino)

at my first test i got stuck with a library i want to use that uses a callback function (function pointers)
after some reasearch i now know that the pointer to a function of an class is not compatible with a pointer to a ‘normal’ function.

i also found that a static function in a class works for function pointers.
but i think i want to use non-static members of the class and than i will again run into trouble…
(as fare as i know i not allowed to mix static and non static things… - and that makes logically sense)

i have setup a minimal example:
on github [edit]and attached[/edit]

the libClass implements a basic timer
→ that fires after x ms and calls the function pointer
and is used two times:

  • in the main ino file
  • and in the moduleClass.

the callback functions are just printing a counter value to the serial port.

currently i have the static variant uncommented.

So for me there are two questions arising:

  • is there a better/other way to do such modularization?
  • what is the best way to do callback functions?

(i can rewrite the used libraries - most of theme are my own…)

hopefully someone of you can bring some light into this :slight_smile:

sunny greetings
stefan

test__callbackfunctions.ino (2.03 KB)

moduleClass.h (998 Bytes)

moduleClass.cpp (1.2 KB)

libClass.h (1.11 KB)

libClass.cpp (1.32 KB)

after some reasearch i now know that the pointer to a function of an class is not compatible with a pointer to a 'normal' function.

That depends on what you mean by "compatible". If "compatible" means interchangeable, then, no they are not.

(as fare as i know i not allowed to mix static and non static things... - and that makes logically sense)

Again, that depends on what you mean by "mix". A static method could call non-static functions IF the static method is passed a pointer to an instance.

i have setup a minimal example on github

Instead of making it easy for you to read by attaching it here...

what is the best way to do callback functions?

The way that accomplishes the desired results, of course.

hi PaulS,
thanks for your feedback.

i have attached the files in the first post.

more specific for my callback thing:
my library is generating events.
my module and or main sketch should react to this.
currently iam doing this with function pointers for the callback function.
for the main sketch that worked well:

relevant part from libClass.h

class libClass {
public:

    typedef void (*cbfunc_t) (uint32_t counter);

    void set_callbackfunc(cbfunc_t callbackfunc_new);

private:

    bool cbfunc_valid;
    cbfunc_t callbackfunc;

};

and parts in libClass.cpp

void libClass::update() {
    uint32_t current_duration = (millis() - lastAction);
    if (current_duration >= duration) {
        // reset
        lastAction = millis();
        // increment internal counter
        counter = counter +1;

        // call callback function
        if (cbfunc_valid) {
            callbackfunc(counter);
        }
    }
}

void libClass::set_callbackfunc(cbfunc_t callbackfunc_new) {
    callbackfunc = callbackfunc_new;
    cbfunc_valid = true;
}

way i used it in my main sketch:

#include "libClass.h"
libClass myLib(10000);  // =10s

void setup() {
    // init other stuff...

    myLib.init();
    myLib.set_callbackfunc(test_cbf);
}

void loop() {
    myLib.update();
}


void test_cbf(uint32_t counter) {
    Serial.print(F("test_cbf - counter:"));
    Serial.println(counter);
}

if i try to use it the same way in an class i get the compiler error:

moduleClass.cpp:43: error: no matching function for call to ‘libClass::set_callbackfunc()’

i have researched this and found that the pointer types from normal functions and ‘member-functions’ of classes are different.
one solution seems to be declaring the member-function static.
but i thought that a static function is not allowed to call member functions or attributes.

you say it is possible if i have a pointer to my instance -
how can i get this pointer and how to looks the call for this?

on thing that came to my mind is that i don’t know if it is the right/best way to do the modules with classes -
in my current concept i only need one instance of every module.
So please if i over-complicate my thing let me know! :wink:

hopefully i have made it easier to understand.

sunny greetings
stefan

one solution seems to be declaring the member-function static. but i thought that a static function is not allowed to call member functions or attributes.

Not necessarily true. Consider this:

SomeClass someInstance; // In the sketch...

// In the class header file
static void SomeStaticMethod(SomeClass *pSomeInstance);
void SomeRegularMethod();

// In the class source file
void SomeStaticMethod(SomeClass *pSomeInstance)
{
   if(pSomeInstance)
     pSomeInstance->SomeRegularMethod();
}

// In the sketch
SomeClass::SomeStaticMethod(*someInstance);

It is possible for a static method to invoke methods on an instance of the class, IF it knows which instance to invoke the methods on.

And THAT is the crux of the problem. When an event happens in your library, how will it know which instance(s) care?

ok - so that means at the moment i set the callback function i would have to store the target reference also.

than i can call the function on the target.

only thing is - i don't know the right syntax for this. (i will experiment with this)

@PaulS have you a tip for me?

sunny greetings stefan

ok - so that means at the moment i set the callback function i would have to store the target reference also.

If "target reference" means the instance that is to deal with the event, then, yes you need to store that at some point. It does not have to be when the callback function is registered. In fact, it probably shouldn't be. That would allow you to change the instance that gets called, or register more than one instance and have all of them get called.

@PaulS have you a tip for me?

Buy low; sell high.