来自 API 的回调成员函数接受一个没有参数的自由函数

Callback member function from API taking a free function with no arguments

我使用一个 API 声明是:

some_API.h

typedef void (CALLBACK   *EventCallback)();

class some_API{
public:
    // ...
    void EnableInterrupt(EventCallback func1, EventCallback func2);
    // ...
};

在另一边我有一个 class 使用这个 API:

My_class.h

class My_class
{
    some_API API;
    void CALLBACK my_func() { cout << "This is my_func!\n" }
public:
    void using_api()
    {
        API.EnableInterrupt(my_func, nullptr);
    }
};

主要问题是 my_func 的类型,错误:

Error (active) E0167 argument of type "void (__stdcall My_class::*)()" is incompatible with parameter of type "EventCallback"

我找到了 this 答案,但问题是 API 是闭源的,所以我无法更改 void EnableInterrupt(EventCallback, EventCallback).

的声明

我也不想将 My_class::using_APIMy_class::API 声明为静态成员。

我想要类似这样的东西:

API.EnableInterrupt((static_cast<void*>(my_func)), nullptr);
// or
API.EnableInterrupt((static_cast<EventCallback>(my_func)), nullptr); // invalid type conversion

有什么方法可以将该成员函数转换为非成员函数以将其传递给 some_API::EnableInterrupt(...)

当您需要 link 回调函数与您的 class 实例时,C 和 C++ 之间的回调工作得很好。为了使其工作,您需要从 C 端传递信息(通常是在回调注册时提供的 void* )。最简单的方法是使用静态方法和 this 指针:

typedef void (*EventCallback)(void*)();
void EnableInterrupt(EventCallback func1, void *userdata);

static void CallbackWrapper(void* data) {
    static_cast<My_class*>(data)->my_func();
}

EnableInterrupt(CallbackWrapper, this)

现在,当你的回调是这样的:

typedef void (CALLBACK   *EventCallback)();

这可能是因为设计不当,也可能是 API 设计者不希望您注册多个回调处理程序。这可能是一个重要的问题,考虑到该函数会干扰中断,如果他们不希望您注册多个处理程序,我会理解。

因此,您只需要创建一个 class 实例,因此单例模式可能是一个不错的选择。为此,您必须执行以下操作:

void CALLBACK global_callback_handler() {
  My_class::getInstance()->my_func()
}

void using_api()
{
    API.EnableInterrupt(global_callback_handler);
}

你的问题是回调类型

typedef void (CALLBACK   *EventCallback)();

没有任何用户提供的数据指针。这是习惯做法,正是因为用户经常需要它。

为了完整起见,

Is there any way to cast that member function to a non-member function

不,它们根本不同,因为必须使用隐式 this 指针调用非静态成员函数,并且没有地方可以将其存储在常规函数指针中。这正是我们使用用户提供的指针参数 for 如果 API 有一个。

Also I don't want to declare My_class::using_API and My_class::API as static member.

如果我们为每个不同的注册生成另一个 static 怎么样?我们可以通过使用 lambda 类型唯一性来自动化它:

template <typename Lambda>
class TrampolineWrapper
{
  inline static std::optional<Lambda> dest_; // optional so we can defer initialization

public:
  TrampolineWrapper(some_API *api, Lambda &&lambda)
  {
    // store your _stateful_ functor into a unique static
    dest_.emplace( std::move(lambda) );

    // register a unique _stateless_ lambda (convertible to free function)
    api->EnableInterrupt(
      [](){
        (*TrampolineWrapper<Lambda>::dest_)();
      },
      nullptr);
  }
};

// handy type-deducing function
template <typename Lambda>
TrampolineWrapper<Lambda> wrap(some_API *api, Lambda &&lambda)
{
    return {api, std::move(lambda)};
}

并像

一样使用它
class My_class
{
    some_API API;
    void my_func() { cout << "This is my_func!\n" }
public:
    void using_api()
    {
        auto wrapper = wrap(&API, [this](){ my_func(); });
       // stateful lambda          ^^^^
       // not directly convertible to a free function
    }
};

这取决于要工作的 lambda 类型的唯一性 - 您使用它的每个 lambda 都会得到一个相同类型的唯一静态 dest_ 对象(可能有状态)和一个独特的无状态转发 lambda(对于这个可怕的 API 可以转换为免费函数)。

请注意,只有当您有多个不同的调用者注册不同的 lambda 时,这才是唯一的。对 My_class::using_api 的两次调用仍然会发生冲突(第二次调用将覆盖第一次的 lambda)。

唯一令人讨厌的事情是,一旦设置了静态,我们就丢弃了包装器……保留它会更好,这样我们就可以用它来注销回调或其他东西。如果你愿意,你可以这样做,但由于你无法知道它在你的 lambda 范围之外的类型,它需要做一些动态的事情。