【问题标题】:C++ idiom when using a C library which expects a function pointer? [duplicate]使用需要函数指针的 C 库时的 C++ 习惯用法? [复制]
【发布时间】:2019-02-14 12:54:19
【问题描述】:

我在 C++ 应用程序中使用 C 库。 C SDK 具有将回调函数指针作为参数的函数。这些函数的签名通常是这样的:

typedef int (* Func) (type1 c, type2 d);

我使用 C++ 中的类构建了我的代码。但是,我不能将任何成员函数作为回调传递给这个函数,因为它不接受int (MyClass::*)(type1 c, type2 d),只接受int (*)(type1 c, type2 d)

我通过在各种类中将所有回调定义为 static 然后将它们传递给 C 库来解决这个问题。

我还是 C++ 新手,所以我不确定这是否是正确的解决方案?该代码有效,但我很想知道我是否做错了。

【问题讨论】:

  • C 回调应采用 void* user_data 以允许使用 C++ 中的类。
  • @ThomasSablik Boost.Bind 如何帮助 C 回调?

标签: c++


【解决方案1】:

在您的代码可能遇到的大多数平台上,您的解决方案都很好。但是,严格来说,可能存在 C 调用约定和 C++ 调用约定不同的平台。在这个平台上,您的代码将无法运行。

100% 正确的解决方案(与您使用的 99.9% 正确的解决方案相反)是定义一个具有 C 语言链接的函数作为回调。如果你需要这个函数来让成员访问你的类,它可以在你的类中调用 static 成员函数:

class MyClass
{
  static int secret;
public:
  static void callback() { secret = 42; }
};

extern "C" void callback() { MyClass::callback(); }

请注意,回调注册点的作者(即,将回调您的库的作者)为用户提供一种将 void* 数据与回调关联的方法被认为是一种良好的做法,这将是他们打电话给你时通过了。如果您的库允许这样做,您可以使用 void *user_data 来传递指向您的 C++ 类的指针。有了这样一个精心设计的回调库,它可能看起来像这样:

class MyClass
{
  int secret;

public:
  void setSecret() { secret = 42; }
};

extern "C" void callback(void *userData) { static_cast<MyClass*>(userData)->setSecret(); }

MyClass mc;

int main()
{
  cLibrary_registerCallback(&callback, &mc);
}

【讨论】:

  • 谢谢!在我理解发生了什么之前,这需要进行相当多的冥想。 SDK 的某些功能确实有 void*,但我没有以正确的方式使用它们。我要看看我是否可以从静态切换到成员函数。
  • 一个后续问题,如果我想将参数传递给回调,我该怎么做。例如,在第二个示例中,如果我想将string 传递给setSecret,即static_cast&lt;MyClass*&gt;(userData)-&gt;setSecret("Hello");,该怎么办?
  • @PlastyGrove setSecret 在第二个示例中完全由您控制,您可以随心所欲地使用它。 callback 的签名由库决定(因为它是调用它的人)。如果您需要在回调“旁边”存储一些数据(以便回调在调用时接收它),您必须使其成为userData 的一部分。例如,让userData 不指向MyClass,而是指向std::pair&lt;MyClass*, AdditionalPayload&gt; 或类似的东西(当然要正确管理它的生命周期)。
  • 我明白了,让我试一试。我想我必须创建一些成员级别的std::pair 变量并分配this,然后再将其传递给回调寄存器函数。谢谢!
【解决方案2】:

成员函数总是隐式地获取 'this' 作为它们的第一个参数。静态函数的使用通常是因为静态函数不需要这个隐式参数。请参阅 Angew 对 extern "C" 的回答以获得 100% 的可移植性。

【讨论】:

  • 这是正确的,但它没有解决语言链接问题。 C++ 函数不需要使用与 C 函数相同的调用约定,因此,正如其他答案所指出的那样,为了实现最大的可移植性,回调应该是标记为 extern "C" 的非成员函数。
  • @PeteBecker 是的,你是对的。我编辑了答案以反映这一点。
【解决方案3】:

要将非静态成员函数作为回调调用,您需要定义一个非成员函数,该函数接受一个实例作为参数,并在该实例上调用该成员函数。使用函数指针回调的此类 C API 还允许注册一个指向用户定义数据的 void 指针,该数据将被转发给回调。使用此指针传递实例。

通常使用 lambda 代替命名函数,因为这种简单的包装器通常不可重用并且不需要名称。

【讨论】:

  • 并且那个非成员函数应该被标记为extern "C"
猜你喜欢
  • 2014-10-03
  • 2017-05-03
  • 1970-01-01
  • 1970-01-01
  • 2021-11-14
  • 2011-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多