Какие виды функций 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 ++ используются разные соглашения о вызовах и языковые привязки.

5

Решение

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

Библиотека 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?

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

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

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

Нет.

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

Нет.

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

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

4

Другие решения

В общем, если вы не используете 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 *);
2

По вопросам рекламы [email protected]