Как передать нестатический член класса в обратный вызов?

Я использую библиотеку arduinoWebSockets в своей собственной библиотеке Arduino. У меня возникли проблемы с попыткой назначить член класса в качестве функции обратного вызова onEvent библиотеки веб-сокетов.

MyClass.cpp:

#include <WebSocketsClient.h>
#include "Arduino.h"
#include "MyClass.h"

void MyClass::connect(String host, int port) {
  webSocket.begin(host, port, "/");
  webSocket.setReconnectInterval(5000);

  // попробовал все эти варианты
  // webSocket.onEvent(incomingEventHandler);
  // webSocket.onEvent(MyClass::incomingEventHandler);
  webSocket.onEvent(this->incomingEventHandler);
}

void MyClass::loop() {
  webSocket.loop();
}

void MyClass::incomingEventHandler(WStype_t type, uint8_t *payload, size_t length) {
  // делаем что-то с входящими данными
  // нужен доступ к другим функциям-членам и переменным класса
}

MyClass.h:

#ifndef MyClass_h
#define MyClass_h

#include <WebSocketsClient.h>
#include "Arduino.h"

class MyClass {
  public:
    void connect(String host, int port);
    void loop();
    void incomingEventHandler(WStype_t type, uint8_t *payload, size_t length);
  private:
    WebSocketsClient webSocket;
};

#endif

Возвращается ошибка:

sketch/MyClass.cpp: в функции-члене void MyClass::connect(String, int)': MyClass.cpp:14:47: ошибка: нет соответствующей функции для вызова 'WebSocketsClient::onEvent()'
webSocket.onEvent(this->incomingEventHandler);


Насколько я понимаю, мне нужно создать указатель на член класса. Я пробовал несколько разных способов, этот показался наиболее многообещающим, но все равно не удалось:

typedef void (MyClass::*MyClassMemFn)(WStype_t type, uint8_t *payload, size_t length);

void MyClass::connect(String host, int port) {
  webSocket.begin(host, port, "/");
  webSocket.setReconnectInterval(5000);

  MyClassMemFn onEventCallback = &MyClass::incomingEventHandler;
  webSocket.onEvent(MyClassMemFn onEventCallback);
}

Приведенное выше возвращает эту ошибку:

sketch/MyClass.cpp: в функции-члене void MyClass::connect(String, int)': MyClass.cpp:15:34: ошибка: ожидалось первичное выражение раньше 'onEventCallback' webSocket.onEvent(MyClassMemFn onEventCallback);


Я также пытался использовать std::bind:

void MyClass::connect(String host, int port) {
  webSocket.begin(host, port, "/");
  webSocket.setReconnectInterval(5000);

  webSocket.onEvent(std::bind(&MyClass::incomingEventHandler, this));
}

Ошибка:

sketch/MyClass.cpp: в функции-члене void MyClass::connect(String, int)': MyClass.cpp:12:47: ошибка: нет соответствующей функции для вызова 'WebSocketsClient::onEvent()'
webSocket.onEvent(this->incomingEventHandler);


Если я перенесу функцию incomingEventHandler за пределы определения класса, она будет работать как положено, однако тогда я не смогу вызывать другие члены класса или получать доступ к свойствам класса в той же области видимости.

Как в Arduino C++ передать нестатические свойства членов класса в качестве обратного вызова? Есть ли лучший способ подойти к этому?

, 👍2

Обсуждение

@asked На самом деле это вопрос о том, как работает C++. Магия заключается в обработке this. Функции-члены имеют "скрытый" параметр. This необходим для предоставления доступа к данным-членам и таблице виртуальных функций. Поэтому краткий ответ на ваш вопрос таков: обратный вызов не может быть функцией-членом., @Mikael Patel


1 ответ


Лучший ответ:

7

Да, обратный вызов может быть функцией-членом. Ошибка в вашем вызове std::bind заключается в том, что, хотя вы привязываете первый скрытый параметр this к экземпляру объекта, в данном случае this объект, вы также должны использовать заполнители для всех других параметров функции.

Поскольку вы заявили

void incomingEventHandler(WStype_t type, uint8_t *payload, size_t length)

Вы должны привязаться как

webSocket.onEvent(std::bind(&MyClass::incomingEventHandler, this,  std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

Обратите внимание, что этот вызов webSocket.onEvent работает только в том случае, если вы используете платформу, отличную от AVR, поскольку библиотека определяет

void WebSocketsClient::onEvent(WebSocketClientEvent cbEvent) {
    _cbEvent = cbEvent;
}

с

#ifdef __AVR__
        typedef void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length);
#else
        typedef std::function<void (WStype_t type, uint8_t * payload, size_t length)> WebSocketClientEvent;
#endif

т.е. это необработанный указатель на функцию в __AVR__, но std::function на платформах, отличных от AVR — std::bind даст вам std::function. Поскольку у вас ESP8266, все должно работать нормально.

Обратитесь к https://en.cppreference.com/w/cpp/utility/functional/bind (и, возможно, мой старый вопрос)< /п>

,