C++ 在 freeRTOS xTimerCreate 中使用 class 非静态方法作为函数指针回调

C++ Use a class non-static method as a function pointer callback in freeRTOS xTimerCreate

我正在尝试使用 marvinroger/async-mqtt-client,在提供的示例中,它与 freertos 计时器一起使用,后者使用在计时器到期时调用的回调。完整的例子是 here.

我想创建一个单例 class 来包含所有连接管理部分,并只公开构造函数(通过 getInstance)和一个开始函数,除了设置回调之外,它还创建用于重新连接的计时器。

class 看起来像(我通过删除无用部分进行了简化):

class MqttManager : public Singleton<MqttManager> {

public:
    virtual ~MqttManager() = default;
    void begin();

protected:
    MqttManager();

    void connectToMqtt(TimerHandle_t xTimer);

    void WiFiEvent(WiFiEvent_t event);
    void onConnect(bool sessionPresent);

    std::unique_ptr<AsyncMqttClient> client;
    TimerHandle_t mqttReconnectTimer;
    TimerHandle_t wifiReconnectTimer;
};

虽然我的问题是当我尝试将 connectToMqtt 回调传递给计时器时。

MqttManager::MqttManager() {
    this->client = std::unique_ptr<AsyncMqttClient>(new AsyncMqttClient());

    // use bind to use a class non-static method as a callback
    // (works fine for mqtt callbacks and wifi callback)
    this->client->onConnect(std::bind(&MqttManager::onConnect, this, std::placeholders::_1));
    WiFi.onEvent(std::bind(&MqttManager::WiFiEvent, this, std::placeholders::_1));
      
    // Here it fails
    mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)nullptr, &MqttManager::connectToMqtt, this, std::placeholders::_1);

错误是:

cannot convert 'void (MqttManager::)(TimerHandle_t) {aka void (MqttManager::)(void*)}' to 'TimerCallbackFunction_t {aka void ()(void)}' for argument '5' to 'void* xTimerCreate(const char*, TickType_t, UBaseType_t, void*, TimerCallbackFunction_t)'

现在,从这里开始,考虑到问题在于有一个指向需要以某种方式转换为自由函数指针的非静态方法的指针,出现了三个疑问:

  1. 为什么 std::bind“方法”适用于 WiFi.onEvent 但不适用于 xTimerCreate?他们看起来和我很相似……WiFi是typedef void (*WiFiEventCb)(system_event_id_t event);,而定时器是typedef void (*TimerCallbackFunction_t)( TimerHandle_t xTimer );
  2. 我怎样才能完成这项工作?有演员表或更好的方法吗?
  3. 这是不好的做法吗?我的目标是将 mqtt 和 wifi 函数和回调包含在一个整洁的 class 中,易于识别、组织和维护;但我想有时候你只是在没有注意到的情况下得到了相反的结果...

std::bind returns 可调用对象,不是函数指针。它与 WiFi.onEvent 一起工作,因为有一个重载需要 std::function:

typedef std::function<void(arduino_event_id_t event, arduino_event_info_t info)> WiFiEventFuncCb;
// ...
wifi_event_id_t onEvent(WiFiEventFuncCb cbEvent, arduino_event_id_t event = ARDUINO_EVENT_MAX);

解决方案

为计时器回调创建一个静态函数,然后像从其他任何地方一样简单地获取 MqttManager 实例。

  1. FreeRTOS 代码是普通的旧 C。它对 C++、实例方法、函数对象等一无所知。它接受一个指向函数的指针,句点。正如 Armandas 指出的那样,WiFi.onEvent 另一方面是 C++,由某人精心编写以接受 std::bind().

    的输出
  2. 有一个解决方法。当您阅读 xTimerCreate API docs 时,有一个偷偷摸摸的小参数 pvTimerID,它实际上是 user-specified 数据。您可以使用它来传递指向您的 class 的指针,然后使用 pvTimerGetTimerID() 从回调函数中检索它。使用 class 指针,您可以将回调转发给您的 C++ class。请参阅下面的示例。

  3. 尝试隐藏私有 class 方法和数据是一种很好的做法。不幸的是,这仅在您完全使用 C++ 工作时才有效:) 如果调用 C 库(如 FreeRTOS),我发现自己偶尔会打破这种理想主义原则。

这是我的做法。我使用 lambda(没有上下文)作为实际的回调函数,因为它是一次性包装代码,C 库很乐意接受它作为一个普通的旧函数指针。

auto onTimer = [](TimerHandle_t hTmr) {
  MqttManager* mm = static_cast<MqttManager*>(pvTimerGetTimerID(hTmr)); // Retrieve the pointer to class
  assert(mm); // Sanity check
  mm->connectToMqtt(hTmr); // Forward to the real callback
}

mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, static_cast<void*>(this), onTimer);