Pass a callback function with a parameter to a function

18.3k views Asked by At

I want to call the following function and pass it a function with a parameter. The purpose of that is that it should call the function with my specified parameter so I know what triggered the function (in that case a gpio pin on the Raspberry Pi).

int wiringPiISR( int pin, int edgeType, void (*function)( void ) );

Currently I have:

for ( int i = 0; i < myValues.size(); ++i )
{
    int myValue = myValues[ i ];
    wiringPiISR( myValue, INT_EDGE_RISING, &myCallback( myValue ) );
}

Though this is giving me the following error:

error: lvalue required as unary ‘&’ operand

Which I can't really understand as to my understanding, myValue is an lvalue or is it not?

Is it what I want do even possible? If so how? The function wiringPiISR is from a library called wiringPi and I would like to avoid modifying it as much as possible.

6

There are 6 answers

7
Roddy On BEST ANSWER

You could combine the answers from imreal and Ryan Haining something like this.

std::function<void()> cbfunc;

void myCallback()
{
  cbfunc();
}
void myWiringPiISR(int val, int mask, std::function<void()> callback)
{
  cbfunc = callback;
  wiringPiISR(val, mask, &myCallback);
}

... and then use it...

void myActualCallback(int v)
{
  ... do something...
}

myWiringPiISR(myValue, INT_EDGE_RISING, std::bind(myActualCallback, myValue));

No need to patch library, and you can use all the bind/function goodness. I'll leave you to find a way around the thread safety issues...

How does it work? Put simply 'std::bind' is binding together a function and it's parameters into a single std:function object which can then be 'called' from the myCallback function which acts as a shim around the callback that you pass. I'd given the callback function a confusing name before, but this edit has hopefully fixed that.

0
twentylemon On

If you have c++11, I suggest using std::function - it's quite a bit cleaner.

If not, your function signature is wrong. You want a callback with the type void(int) but your function takes a void()

0
David van rijn On

I looked up wiringPiISR and found that it is some sort of api call, so i am assuming you cannot change it.

Having said that, there is a reason most api-calls with a function-pointer-callback look sort of like this

void setCallback( void (*function)(void* data), void* userdata);

This allows people to cast their struct {blabla} data; to add some userdata, and when the function is called, it is passed along.

So basically, apart from hacking stuff with static variables, you can't pass any arguments.

3
imreal On

You need to use std::function and std::bind.

Change your function signature to

int wiringPiISR (int pin, int edgeType,  std::function<void()> func);

Inside you can call the callback simply using func()

And your call to:

int myValue = 3;
wiringPiISR(myValue, INT_EDGE_RISING, std::bind(myCallback, myValue));

What this does is create a std::function object (i.e. a callable) that wraps your function and keeps your desired value in its state.

This will only work on C++11 and newer.

5
Ryan Haining On

If myValue is something you can decide at compile time, you could set it statically and use an intermediate function to pass in.

void myCallbackHelper() {
    static constexpr int myValue = 3;
    myCallback(myValue);
}

wiringPiISR(myValue, INT_EDGE_RISING, &myCallbackHelper);

If you need to determine myValue at run time, you could still accomplish this, but not really thread-safely.

int& getMyValue() {
    static int myValue;
    return myValue;
}

void setMyValue(int i) {
    getMyValue() = i;
}

void myCallbackHelper() {
    myCallback(getMyValue());
}

Then set it and call

setMyValue(3);
wiringPiISR(myValue, INT_EDGE_RISING, &myCallbackHelper);
3
Puppy On

You can "vomit" the function. This doesn't require a user-defined mutable global variable and is thread-safe, unless you have a compiler that supports multiple threads but not per-thread exceptions which would be basically unusable.

myWiringPiISRWrapper(Value value, int edge, std::function<void()> func) {
    try {
        throw &func;
    } catch(...) {
        myWiringPiISR(value, edge, [] {
            try {
                throw;
            } catch(std::function<void()>* func) { 
                (*func)();
            }
        });
    }
}

It's disgusting and slow, but it's totally encapsulated which I think is a worthwhile upside. Note that this only works if the callback is never executed after the call to myWiringPiISR returns. In this case you can of course have a callback with whatever bound state you desire.