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
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.
-
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.Vasil Kalchev– Vasil Kalchev2016年04月17日 07:12:49 +00:00Commented Apr 17, 2016 at 7:12
-
You are a life saver my man!!!quickshiftin– quickshiftin2018年09月12日 09:27:59 +00:00Commented Sep 12, 2018 at 9:27
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).
-
Thank you for the answer, I went with the other method, because it was closer to what I needed.Vasil Kalchev– Vasil Kalchev2016年04月17日 07:15:56 +00:00Commented Apr 17, 2016 at 7:15