Какие виды функций C++ могут быть помещены в указатель функции C?

У меня есть библиотека C, которая использует структуру указателей на функции для обратных вызовов. Обратные вызовы будут вызываться из кода Си.

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

Какие виды функций C++ я могу безопасно разместить в тех указателях функций, которые будут вызываться из библиотеки C? Статические функции-члены? Полностью определенные функции шаблона? Не захватывая лямбды?

G ++, по-видимому, позволяет мне использовать все вышеперечисленное, но я подвергаю сомнению безопасность, когда для функций C и C++ используются разные соглашения о вызовах и языковые привязки.

2 ответа

Решение

У меня есть библиотека C, которая использует структуру указателей на функции для обратных вызовов. Обратные вызовы будут вызываться из кода Си.

Библиотека AC понимает только 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?

Статические функции-члены?

Нет. Но это работает на многих платформах. Это распространенная ошибка, и она будет кусать людей на некоторых платформах.

Полностью определенные функции шаблона?

Нет.

Не захватывая лямбды?

Нет.

G ++, похоже, позволяет мне использовать все вышеперечисленное,

Да. Но предполагается, что вы переходите к объекту, созданному с помощью компилятора C++ (что было бы допустимо). В качестве кода C++ будет использоваться правильное соглашение о вызовах. Проблема в том, что вы передаёте эти вещи в библиотеку C (на ум приходит pthreads).

В общем, если вы не используете cast, вы должны доверять g++.

Хотя верно, что ни один из упомянутых вами типов функций не может быть экспортирован для использования из C, это не то, о чем вы просите. Вы спрашиваете о том, какие функции вы можете передать как указатель на функцию.

Чтобы ответить на то, что вы можете пройти, я думаю, что более конструктивно понять, что вы не можете пройти. Вы не можете передать ничего, что требует дополнительных аргументов, явно не указанных в списке аргументов.

Итак, нет нестатических методов. Им нужно скрытое "это". С не узнает, чтобы передать его. Опять же, компилятор не позволит вам.

Нет захвата лямбды. Они требуют неявного аргумента с фактическим лямбда-телом.

То, что вы можете передать, это указатели на функции, которые не требуют неявного контекста. На самом деле, вы пошли дальше и перечислили их:

  • Указатель на функцию. Неважно, является ли это стандартная функция или шаблон, если шаблон полностью разрешен. Это не является проблемой. Любой написанный вами синтаксис, который приводит к указателю на функцию, автоматически полностью разрешает шаблон.
  • Не захватывая лямбды. Это специальный обходной путь, введенный C++11, когда он представил лямбда-выражения. Поскольку это возможно, компилятор выполняет явное преобразование, необходимое для того, чтобы это произошло.
  • Статические методы. Так как они статичны, они не получают неявного 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, эта схема потерпела бы крах.

Это, однако, не уникально для статических методов, лямбд и шаблонных функций. Даже стандартная функция потерпит неудачу при таких обстоятельствах.

К сожалению, когда вы определяете указатель функции на тип 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 *);
Другие вопросы по тегам