简体   繁体   中英

Using C++ member function to handle a callback that takes simple static function pointer

I haven't used C++ in ages. Between what I've forgotten and what has changed in C++ over time, I'm really banging my head against the wall trying to do something that would be trivially easy in JavaScript, or any other language where functions are objects, and not just simple pointers.

I think I understand the basic problem: A class member function only exists in once place in memory (there isn't a different copy of it for each class instance). The only way the function knows what "this" is is because an instance pointer is passed along as an invisible first argument to every function call. A plain-old C-style callback isn't going to know anything about passing that instance pointer.

What I need is a new function that is somehow bound to my class instance, one which knows how to pass "this" along to the member function. That's the function I need to use as a callback.

But I don't know for sure how to dynamically create such a function. I think the code below is on the right track (except for casting pointer types), but it does bother me a bit because it seems like that there'd have to be some dynamic memory allocation going on, and if so, some way to track that allocation and do clean-up later.

class SignalMonitor {
    int dataPin;
    unsigned short timings[RING_BUFFER_SIZE];
    unsigned long lastSignalChange = 0;
    int dataIndex = 0;
    int syncCount = 0;

    void signalHasChanged();

  public:
    SignalMonitor(int);
};

SignalMonitor::SignalMonitor(int dataPin) {
  this->dataPin = dataPin;
  function<void()> callback = bind(&SignalMonitor::signalHasChanged, this);
  wiringPiISR(dataPin, INT_EDGE_BOTH, callback);
}

void SignalMonitor::signalHasChanged() {
  unsigned long now = micros();
  int duration = (int) min(now - this->lastSignalChange, 10000ul);
  this->lastSignalChange = now;
  cout << duration << '\n';
}

I feel like this is close to what I want, but I'm getting this error:

acu-rite-433Mhz-reader.cpp:58:72: error: invalid cast from type ‘std::function<void()>’ to type ‘void*’
   wiringPiISR(dataPin, INT_EDGE_BOTH, reinterpret_cast<void *>(callback));
                                                                        ^

Here's the call signature of the function I'm trying to pass this callback to:

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

I've found a number of similar issues discussed when searching on this topic, but they either don't quite match what I'm trying to do, or assume much more familiarity with C++ than I currently possess. (All I remember about function pointer types is that they can get hellishly ugly very quickly!)

I tried to use lambda function as a solution, but that led to an error (besides a type mismatch error) about something being "temporary", which I'm assuming meant that the lambda function's scope was temporary.

It may help to consider the traditional way of handling a similar issue. Other APIs have been designed where instead of void(*function)(void) , wiringPiISR would expect a function void(*function)(void *) . This allows the use of

static void signalHasChanged(void *p) {
  static_cast<SignalMonitor*>(p)->signalHasChanged();
}

This is not a general solution, but because Raspberry Pi has a limited number of GPIO pins, and you can't have more callback functions than you have pins, you might be able to create one callback function per pin. Then, you need a global data structure that maps the interrupt pin to which SignalMonitor instance (or instances) it should signal. The constructor would register the 'this' object to a specific pin, then set the appropriate callback function based on the pin.

The callback functions would be able to pass a pin argument to a general function, which could then look up the specific SignalMonitor instance and call a class function.

I wouldn't want to do it for 1000 pins, 1000 instances, but this hack should work for anything running on a Pi.

This is a far from ideal solution (I'm beginning to think there are no ideal solutions here), but it works for me in this particular case where there aren't likely to be very many instances of my SignalMonitor class in use at the same time.

First, I turned my signalHasChanged class method into a static method that takes an instance as an argument. (I could have kept the method as a class method by going through some hairy type-casting, but it wasn't worth it.)

Then I made ten almost-identical indirect callback functions:

void smCallback0() { SignalMonitor::signalHasChanged(monitors[0]); }
void smCallback1() { SignalMonitor::signalHasChanged(monitors[1]); }
void smCallback2() { SignalMonitor::signalHasChanged(monitors[2]); }
void smCallback3() { SignalMonitor::signalHasChanged(monitors[3]); }
void smCallback4() { SignalMonitor::signalHasChanged(monitors[4]); }
void smCallback5() { SignalMonitor::signalHasChanged(monitors[5]); }
void smCallback6() { SignalMonitor::signalHasChanged(monitors[6]); }
void smCallback7() { SignalMonitor::signalHasChanged(monitors[7]); }
void smCallback8() { SignalMonitor::signalHasChanged(monitors[8]); }
void smCallback9() { SignalMonitor::signalHasChanged(monitors[9]); }

Then I stuck all of those functions into an array:

void (*_smCallbacks[MAX_MONITORS])() = {
  smCallback0, smCallback1, smCallback2, smCallback3, smCallback4,
  smCallback5, smCallback6, smCallback7, smCallback8, smCallback9
};

Along with the monitors array, which is an array of SignalHandler pointers, this gives me ten available callback slots. ( _smCallbacks is copied into smCallbacks as a way to get around foreward reference problems.)

The init method for SignalMonitor simply searches for an available slot, plugs itself in, then sets the callback:

void SignalMonitor::init() {
  for (int i = 0; i < MAX_MONITORS; ++i) {
    if (monitors[i] == NULL) {
      callbackIndex = i;
      monitors[i] = this;
      break;
    }
  }

  if (callbackIndex < 0)
    throw "Maximum number of SignalMonitor instances reached";

  wiringPiISR(dataPin, INT_EDGE_BOTH, smCallbacks[callbackIndex]);
}

There's also a destructor to free up the callback slots:

SignalMonitor::~SignalMonitor() {
  if (callbackIndex >= 0)
    monitors[callbackIndex] = NULL;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM