Использование функции-члена C ++ для обработки обратного вызова, который принимает простой указатель на функцию stati c - PullRequest
2 голосов
/ 28 января 2020

Я давно не использовал C ++. Между тем, что я забыл, и тем, что со временем изменилось в C ++, я действительно бьюсь головой о стену, пытаясь сделать что-то, что было бы тривиально легко в JavaScript или любом другом языке, где функции - это объекты, а не просто простые указатели.

Мне кажется, я понимаю основную проблему c: функция-член класса существует только в одном месте в памяти (ее не существует для каждого экземпляра класса). Единственный способ, которым функция знает, что такое this, заключается в том, что указатель экземпляра передается в качестве невидимого первого аргумента каждому вызову функции. Обычный обратный вызов в стиле C не будет ничего знать о передаче этого указателя экземпляра.

Мне нужна новая функция, которая каким-то образом связана с моим экземпляром класса, та, которая знает, как передать «это» функции-члену. Это функция, которую мне нужно использовать в качестве обратного вызова.

Но я не знаю точно, как динамически создать такую ​​функцию. Я думаю, что приведенный ниже код находится на правильном пути (за исключением приведения типов указателей), но это немного беспокоит меня, потому что кажется, что должно произойти некоторое динамическое выделение памяти c, и если так, какой-то способ отследить это распределение и выполнить очистку позже.

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';
}

Мне кажется, это близко к тому, что я хочу, но я получаю эту ошибку:

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));
                                                                        ^

Вот подпись вызова функции, к которой я пытаюсь передать этот обратный вызов:

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

Я обнаружил ряд похожих вопросов, которые обсуждались при поиске по этой теме c, но они либо не совсем соответствуют тому, что я пытаюсь сделать, либо предполагают гораздо большее знакомство с C ++, чем у меня есть в настоящее время. (Все, что я помню о типах указателей на функции, это то, что они очень быстро становятся адски безобразными!)

Я пытался использовать лямбда-функцию в качестве решения, но это привело к ошибке (помимо ошибки несоответствия типов) о чем-то Быть «временным», что, как я предполагаю, означало, что область действия лямбда-функции была временной.

Ответы [ 2 ]

1 голос
/ 28 января 2020

Это далеко не идеальное решение (я начинаю думать, что здесь нет идеальных решений), но оно работает для меня в данном конкретном случае, когда вряд ли будет очень много экземпляров моего SignalMonitor класс используется одновременно.

Сначала я превратил свой метод signalHasChanged class в метод stati c, который принимает экземпляр в качестве аргумента. (Я мог бы сохранить метод как метод класса, выполнив какое-то волосатое приведение типов, но оно того не стоило.)

Затем я сделал десять почти идентичных функций косвенного обратного вызова:

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]); }

Затем я вставил все эти функции в массив:

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

Наряду с массивом monitors, который является массивом указателей SignalHandler, это дает мне десять доступных слотов обратного вызова , (_smCallbacks копируется в smCallbacks как способ обойти проблемы с опережающими ссылками.)

Метод init для SignalMonitor просто ищет доступный слот, подключается, затем устанавливает обратный вызов:

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]);
}

Существует также деструктор для освобождения слотов обратного вызова:

SignalMonitor::~SignalMonitor() {
  if (callbackIndex >= 0)
    monitors[callbackIndex] = NULL;
}
1 голос
/ 28 января 2020

Это может помочь рассмотреть традиционный способ решения аналогичной проблемы. Были разработаны другие API, где вместо void(*function)(void), wiringPiISR ожидала бы функцию void(*function)(void *). Это позволяет использовать

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

. Это не общее решение, а потому что Raspberry Pi имеет ограниченное количество выводов GPIO, и вы не можете иметь больше функций обратного вызова, чем у вас, могут быть возможность создать одну функцию обратного вызова на контакт. Затем вам нужна глобальная структура данных, которая отображает контакт прерывания, на который экземпляр SignalMonitor (или экземпляры) должен сигнализировать. Конструктор должен зарегистрировать объект this для указанного c вывода, а затем установить соответствующую функцию обратного вызова на основе этого вывода.

Функции обратного вызова смогут передавать аргумент вывода общей функции, который затем может найти указанный экземпляр c SignalMonitor и вызвать функцию класса.

Я бы не хотел делать это для 1000 пинов, 1000 экземпляров, но этот хак должен работать для всего, что работает на Pi .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...