У меня есть библиотека C, которая использует структуру указателей на функции для обратных вызовов. Обратные вызовы будут вызываться из кода Си.
extern "C" {
typedef struct callbacks_t {
void (*foo) (const char*);
int (*bar) (int);
} callbacks_t;
}// extern C
Какие виды функций C ++ я могу безопасно место в этих указателях функций, которые будут вызываться из библиотеки C? Статические функции-члены? Полностью определенные функции шаблона? Не захватывая лямбды?
G ++, по-видимому, позволяет мне использовать все вышеперечисленное, но я ставлю под сомнение безопасность, когда для функций C и C ++ используются разные соглашения о вызовах и языковые привязки.
У меня есть библиотека 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).
В общем, если вы не используете cast, вы должны доверять g ++.
Хотя верно, что ни один из упомянутых вами типов функций не может быть экспортирован для использования из C, это не то, о чем вы просите. Вы спрашиваете о том, какие функции вы можете передать как указатель на функцию.
Чтобы ответить на то, что вы можете пройти, я думаю, что более конструктивно понять, что вы не можете пройти. Вы не можете передать ничего, что требует дополнительных аргументов, явно не указанных в списке аргументов.
Итак, нет нестатических методов. Им нужно скрытое «это». С не узнает, чтобы передать его. Опять же, компилятор не позволит вам.
Нет захвата лямбды. Они требуют неявного аргумента с фактическим лямбда-телом.
То, что вы можете передать, это указатели на функции, которые не требуют неявного контекста. На самом деле, вы пошли дальше и перечислили их:
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 *);