6

I want to write a class that allows the user to attach his own function to a member function that listens for messages coming from a wireless module, so that the user's function can be called depending on the message.

I am thinking about using function pointers but I don't know how it will work since I don't know the user's function parameters, return type and number of functions that will be added.

//sketch.ino

<unknown> (*funcPtr)(<unknown>)[<unknown>];

void receive() {
  message.type = 'o';
  if (message.available()) {
    message.receive();
  }
  switch (message.type) {
    case 'p': {
      Serial.println(p);
      break;
    }
    case 'f': {
      funcPtr[message.funcID](message.parameter1, message.parameter2);
      break;
    }
    default: break;
}

void userFunction1() {
  Serial.println("User function 1");
}

int userFunction2(int x) {
  return x * 2;
}

funcPtr[0] = &userFunction1;
funcPtr[1] = &userFunction2;

void setup() {}

void loop() {
  receive();
}

Is this kind of thing possible?

2 Answers 2

6

I don't know the user's function parameters,

A common way for dealing with this issue is to pass a generic pointer to arbitrary data. See below.

return type

It makes little sense to allow for arbitrary return types, as your library would have no way of knowing what to do with the returned value.

and number of functions that will be added.

Either you allocate the callback array statically with a fixed size, or you use dynamic allocation. In the first case, the array size could be decided by the user if it is a template parameter. In the second case, you risk memory fragmentation unless all the callbacks are registered once and for all at program startup, which is a common scenario anyway.


About the generic pointer technique

This is a very common way of defining callbacks in C: a callback contains both a function and a void * pointer to arbitrary data, which will be given as a parameter to the function. It is up to the user to decide if and how he will use this pointer: he could completely ignore it, like he could make it point to the root of a complex data tree.

Here is a simple example:

// A callback contains both a function and a pointer to arbitrary data
// that will be passed as argument to the function.
struct Callback {
    Callback(void (*f)(void *) = 0, void *d = 0)
        : function(f), data(d) {}
    void (*function)(void *);
    void *data;
};

Callback callbacks[3];

// This callback expects an int.
void print_int(void *data)
{
    int parameter = * (int *) data;
    Serial.println(parameter);
}

// This one expects a C string.
void print_string(void *data)
{
    char * parameter = (char *) data;
    Serial.println(parameter);
}

void setup()
{
    Serial.begin(9600);

    // Register callbacks.
    static int seventeen = 17;
    static int forty_two = 42;
    static char hello[] = "Hello, World!";
    callbacks[0] = Callback(print_int, &seventeen);
    callbacks[1] = Callback(print_int, &forty_two);
    callbacks[2] = Callback(print_string, hello);
}

void loop()
{
    // Test all the callbacks.
    for (size_t i = 0; i < 3; i++)
        callbacks[i].function(callbacks[i].data);
    delay(500);
}

Notice that the two callback functions take different types of parameters, only disguised into the same generic pointer. Notice also that print_int is used in two different callbacks with different data. In this example, the data does not change, but this need not be the case: both the callbacks and the rest of the user code could change the data pointed to by the generic pointers. Again, it is up to the user to decide how he will use this facility to communicate between his callbacks and the rest of his code.

2
  • Thank you for your answer and sorry for the delay. I modified it to take the address of the function, send it to the server (without saving it on the Arduino), then sending the address back from the server to the Arduino with information about how to recast it into function pointer. Commented Apr 17, 2016 at 7:12
  • You are a life saver my man!!! Commented Sep 12, 2018 at 9:27
3

You can't call arbitrary functions (with random return types, and any number of arguments) but you can specify a callback function that takes a certain number of specific arguments, and returns a certain thing (or nothing).

See http://www.gammon.com.au/callbacks

Example code:

typedef void (*GeneralMessageFunction) ();

void sayHello ()
  {
  Serial.println ("Hello!");  
  }  // end of sayHello

void sayGoodbye ()
  {
  Serial.println ("Goodbye!");  
  }  // end of sayGoodbye

void checkPin (const int pin, GeneralMessageFunction response); // prototype

void checkPin (const int pin, GeneralMessageFunction response)
  {
  if (digitalRead (pin) == LOW)
     {
     response ();  // call the callback function
     delay (500);  // debounce
     }
  }  // end of checkPin

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  pinMode (8, INPUT_PULLUP);
  pinMode (9, INPUT_PULLUP);
  }  // end of setup

void loop ()
  {
  checkPin (8, sayHello);    
  checkPin (9, sayGoodbye);
  }  // end of loop

I don't know how it will work since I don't know the user's function parameters, return type and number of functions that will be added.

You will need to tell the user what sort of function to write. For example, a function that takes a string (ie. the message) and returns a boolean (whether it was handled or not).

1
  • Thank you for the answer, I went with the other method, because it was closer to what I needed. Commented Apr 17, 2016 at 7:15

Not the answer you're looking for? Browse other questions tagged or ask your own question.