如何使用 C++ 成员函数作为 C 框架的回调函数

Posted

技术标签:

【中文标题】如何使用 C++ 成员函数作为 C 框架的回调函数【英文标题】:How to use a C++ member function as the callback function for a C framework 【发布时间】:2011-07-19 15:20:18 【问题描述】:

有一个支持回调函数类型的 C 库(我无法更改)

void (*callback)(void *appContext, int eventid)

我想设置一个 C++ 函数作为回调。

具体我有以下问题?

    需要在"extern C"块下声明回调函数吗?

    成员函数是否必须是静态的才能成为回调函数?是否可以使用非静态成员函数?如果是,如何?什么时候推荐使用非静态成员函数?

    函数是否为模板函数是否重要?

    非类 C 风格的函数比类成员函数有什么优势吗?

我正在旧的 VC++ 编译器上尝试这些变体,它不支持最新的 C++ 标准。但是代码需要独立于平台,并且应该适用于大多数 C++ 编译器。我想知道回调的推荐做法是什么?

【问题讨论】:

您使用的是什么版本的 C/C++? (即 MSVC、GCC) 我现在正在使用 MSVC。但是代码需要独立于平台,因为它也将被移植到 linux 平台。 【参考方案1】:

回调函数需要在extern "C"下声明吗?

没有。仅当您从 C 中直接调用 C++ 函数而不使用函数指针时才需要 extern "C"。如果使用函数指针,则不需要 extern "C"。

我可以使用非静态成员函数作为回调吗?

没有。 A类的非静态成员函数有一个隐含的第一个参数对应于这个指针。

我可以使用静态成员函数作为回调吗?

是的,只要签名与回调的签名匹配。

函数是否为模板函数有关系吗?

不,只要实例化模板的签名与回调匹配,模板函数就可以用作回调。

【讨论】:

【参考方案2】:

假设appContext 是一个不透明的指针,您将其传递给进行回调的函数,您可以像这样获取特定对象的成员函数的回调:

class myclass 

  void do_something() 
     // call function making the callback using _event_handler
     // as the callback function and the "this" pointer as appContext
  

  // make sure the raw callback uses the correct calling convention (cdecl, stdcall, etc.)
  static void _handle_event(void* appContext, int eventid) 
    // forward the event to the actual object
    static_cast<myclass *>(appContext)->handle_event(eventid);
  

  void handle_event(int eventid) 
     // do object-specific event handling
  

;

几个答案提到 extern "C" 作为要求。这是完全不正确的。 仅当您直接从 C++ 调用 C 函数时,extern "C" 才是必需的。它用于告诉 C++ 编译器“在为此函数生成符号名称时不要应用名称修饰”。您正在将 C++ 函数指针传递给 C 函数。只要调用约定匹配,它就可以正常工作。从不涉及函数的名称。

【讨论】:

【参考方案3】:

如果你的成员函数是静态的,这应该可以工作。

【讨论】:

我需要在extern C块下声明函数吗? 我认为如果您的成员函数是静态的,它将起作用。试试看。 只要成员是静态的,调用约定是一样的,参数列表匹配就行。【参考方案4】:

这并不像在extern "C" 块下声明回调函数那么简单。您需要弄清楚 C 库对其函数使用的调用约定。

我将使用的术语是特定于 Microsoft 的,但同样的规则也应该适用于其他平台。

默认情况下,Visual C++ 编译器生成 C 函数 __stdcall 和 C++ 函数 __cdecl。您不能混合和匹配这些调用约定,因为它们中的每一个都对谁清理堆栈做出了不同的假设。 Here's更详细的解释。

匹配调用约定后,最简单的方法是将 C++ 回调函数声明为命名空间范围的独立函数;或者如果它需要是一个成员函数,那么一个静态成员函数。在这两种情况下,您都应该能够使用std::bind 将指针绑定到类的实例。您甚至可以使用 std::bind 绑定非静态成员函数,但我想不起来语法。

【讨论】:

extern "C" 与 stdcall/cdecl 问题完全正交。无论 C 的默认约定是什么,extern "C" 都会根据标准自动使用它。 @n.m.:谢谢,我不知道。但是,OP 仍然需要弄清楚调用约定以使用成员函数作为回调,因为不能指定 extern "C" 这就是为什么不能将成员函数直接用作 C 回调的原因。 这不是真的,如果它是静态成员函数,你很容易做到。 这不是标准的,可能是不可移植的。【参考方案5】:

    确保它在全局范围内

    使用extern "C"

    如果需要,请使用__cdeclvoid (_cdecl *callback)

【讨论】:

(1) 和 (2) 不是必需的,也不需要。您通过指针而不是名称传递函数。 (3) 与调用约定有关,这确实很重要。它必须与对方的期望相匹配,但可以是“__stdcall”以及“__cdecl”或您平台上的任何其他调用约定。【参考方案6】:

严格来说,你不能。必须将 C 回调指定为 extern "C",这对于成员函数是不可能的。只能使用独立功能。您可以从那里将调用转发到任何 C++ 函数,包括静态或非静态成员函数。

根据平台的不同,您有时可以跳过extern "C" 而侥幸逃脱,但我不会测试我的运气。

【讨论】:

以上是关于如何使用 C++ 成员函数作为 C 框架的回调函数的主要内容,如果未能解决你的问题,请参考以下文章

使用 C++ 类成员函数作为 C 回调函数

如何使用 C++ lambda 将成员函数指针转换为普通函数指针以用作回调

C++中类成员函数作为回调函数

C++ 成员函数作为外部库的回调函数

让 C 回调调用 C++ 成员函数的最佳方法?

C#/C++ 回调类(非函数)互操作 - 如何?