7

我有一个 C 库,它使用函数指针结构进行回调。回调将从 C 代码中调用。

extern "C" {
typedef struct callbacks_t {
    void (*foo) (const char*);
    int  (*bar) (int);  
} callbacks_t;
}// extern C

我可以将哪些类型的 C++ 函数安全地放置在要从 C 库调用的函数指针中?静态成员函数?完全指定的模板函数?非捕获 Lambda?

g++ 似乎让我可以使用以上所有内容,但我质疑将不同的调用约定和语言绑定用于 C 和 C++ 函数时的安全性。

4

2 回答 2

7

我有一个 C 库,它使用函数指针结构进行回调。回调将从 C 代码中调用。

AC 库只理解 C。所以你只能传回 C 明确支持和理解的东西。

由于在 C++ 中没有为任何函数类型定义调用约定,因此您不能显式地传回 C++ 函数。您只能传回 C 函数(用 显式声明的函数extern "C")并保证它们是兼容的。

像所有未定义的行为一样,这似乎可以工作(比如传回普通的 C++ 函数或静态成员)。但就像所有未定义的行为一样,它允许工作。您只是不能保证它实际上是正确的或可移植的。

extern "C" {
    typedef struct callbacks_t {
        void (*foo) (const char*);
        int  (*bar) (int);  
    } callbacks_t;

    // Any functions you define in here.
    // You can set as value to foo and bar.

}// extern C

我可以将哪些类型的 C++ 函数安全地放置在要从 C 库调用的函数指针中?

静态成员函数?

不,但这恰好适用于很多平台。这是一个常见的错误,会在某些平台上咬人。

完全指定的模板函数?

不。

非捕获 Lambda?

不。

g++ 似乎让我可以使用以上所有内容,

是的。但它假设您传递给使用 C++ 编译器构建的对象(这将是合法的)。因为 C++ 代码将使用正确的调用约定。问题是当您将这些东西传递给 C 库时(想到 pthreads)。

于 2016-04-29T21:17:38.800 回答
3

一般来说,除非你使用 cast,否则你应该信任 g++。

虽然您提到的任何函数类型都不能导出以在 C 中使用,但这不是您要问的。您在询问可以作为函数指针传递的函数。

要回答你能通过什么,我认为理解你不能通过什么更有建设性。您不能传递任何需要参数列表中未明确说明的附加参数的内容。

所以,没有非静态方法。他们需要一个隐含的“this”。C不会知道通过它。再说一次,编译器不会让你。

没有捕获 lambdas。它们需要一个带有实际 lambda 主体的隐式参数。

您可以传递的是不需要隐式上下文的函数指针。事实上,您继续列出它们:

  • 函数指针。无论是标准函数还是模板都没有关系,只要模板完全解析即可。这不是问题。您编写的任何导致函数指针的语法都将自动完全解析模板。
  • 非捕获 lambdas。这是 C++11 在引入 lambda 时引入的一种特殊解决方法。由于可以这样做,因此编译器会进行显式转换以使其发生。
  • 静态方法。由于它们是静态的,它们不会通过隐式传递this,所以它们没问题。

最后一个值得扩展。许多 C 回调机制得到一个函数指针和一个 void* opaq。以下是在 C++ 类中使用它们的标准且相当安全:

class Something {
  void callback() {
    // Body goes here
  }

  static void exported_callback(void *opaq) {
    static_cast<Something*>(opaq)->callback();
  }
}

然后做:

Something something;

register_callback(Something::exported_callback, &something);

编辑添加: 这有效的唯一原因是,当没有传递隐式参数时,C++ 调用约定和 C 调用约定是相同的。名称修饰存在差异,但在传递函数指针时并不相关,因为名称修饰的唯一目的是允许链接器找到正确的函数地址。

如果您尝试使用期望回调的技巧,例如 stdcall 或 pascal 调用约定,那么该方案将一蹶不振。

然而,这并不是静态方法、lambda 和模板函数所独有的。在这种情况下,即使是标准功能也会失败。

可悲的是,当您定义一个指向 stdcall 类型的函数指针时,gcc 会忽略您:

#define stdcall __attribute__((stdcall))
typedef stdcall void (*callback_type)(void *);

结果是:

test.cpp:2:45: warning: ‘stdcall’ attribute ignored [-Wattributes]
 typedef stdcall void (*callback_type)(void *);
于 2016-04-29T17:34:49.090 回答