19.8.3. Директивы компоновки: extern "C"
Иногда в программах С++ необходимо применять функции, написанные на другом языке программирования. Как правило, это язык С. Подобно любому имени, имя функции, написанной на другом языке, следует объявить. Это объявление должно указать тип возвращаемого значения и список параметров. Компилятор проверяет обращения к внешним функциям на другом языке точно так же, как и обращения к обычным функциям языка С++. Однако для вызова функций, написанных на других языках, компилятор обычно вынужден создавать иной код. Чтобы указать язык для функций, написанных на языке, отличном от С++, используются директивы компоновки (linkage directive).
Комбинация кода С++ с кодом, написанным на любом другом языке, включая язык С, требует доступа к компилятору этого языка, совместимому с вашим компилятором С++.
Объявление функций, написанных на языке, отличном от С++
Директива компоновки может существовать в двух формах: одиночной и составной. Директивы компоновки не могут располагаться в определении класса или функции. Некоторые директивы компоновки должны присутствовать в каждом объявлении функции.
В качестве примера рассмотрим некоторые из функций языка С, объявленные в заголовке cstdlib:
// гипотетические директивы компоновки, которые могли бы
// присутствовать в заголовке С++ <cstring>
// одиночная директива компоновки
extern "С" size_t strlen(const char *);
// составная директива компоновки
extern "С" {
int strcmp(const char*, const char*);
char *strcat(char*, const char*);
}
Первая форма состоит из ключевого слова extern, сопровождаемого строковым литералом и "обычным" объявлением функции.
Строковый литерал указывает язык, на котором написана функция. Используемый компилятор обязан поддерживать директивы компоновки для языка С. Компилятор может поддерживать директивы компоновки и для других языков, например extern "Ada", extern "FORTRAN" и т.д.
Директивы компоновки и заголовки
Та же директива компоновки может быть применена к нескольким функциям одновременно. Для этого их объявления заключают в фигурные скобки после директивы компоновки. Эти фигурные скобки служат для группировки объявлений, к которым применяется директива компоновки. Эти фигурные скобки игнорируются, а имена функций, объявленных в их пределах, видимы, как будто функции были объявлены вне фигурных скобок.
Составная форма объявления применима ко всему файлу заголовка. Например, заголовок cstring языка С++ может выглядеть следующим образом.
// составная директива компоновки
extern "С" {
#include <string.h> // функции языка С, манипулирующие строками
// в стиле С
}
Когда директива #include заключена в фигурные скобки составной директивы компоновки, все объявления обычных функций в файле заголовка будут восприняты как написанные на языке, указанном в директиве компоновки. Директивы компоновки допускают вложенность, т.е. если заголовок содержит функцию с директивой компоновки, на данную функцию это не повлияет.
Функции, унаследованные языком С++ от языка С, могут быть определены как функции языка С, но это не является обязательным условием для каждой реализации языка С++.
Указатели на функции, объявленные в директиве extern "С"
Язык, на котором написана функция, является частью ее типа. Чтобы объявить указатель на функцию, написанную на другом языке программирования, следует использовать директиву компоновки. Кроме того, указатели на функции, написанные на других языках, следует объявлять с той же директивой компоновки, что и у самой функции:
// pf указывает на функцию С, возвращающую void и получающую int
extern "С" void (*pf) (int);
Когда указатель pf используется для вызова функции, созданный при компиляции код подразумевает, что происходит обращение к функции С.
Тип указателя на функцию С не совпадает с типом указателя на функцию С++. Указатель на функцию С не может быть инициализирован (или присвоен) значением указателя на функцию С++ (и наоборот). Как и при любом другом несовпадении типов, попытка присвоения указателя с другой директивой компоновки приведет к ошибке:
void (*pf1)(int); // указатель на функцию С++
extern "С" void (*pf2)(int); // указатель на функцию С
pf1 = pf2; // ошибка: pf1 и pf2 имеют разные типы
Некоторые компиляторы С++ могут допускать присвоение, приведенное выше, хотя, строго говоря, оно некорректно.
Директивы компоновки применимы ко всем объявлениям
Директива компоновки, использованная для функции, применяется также и к любым указателям на нее, используемым как тип возвращаемого значения или параметр.
// f1() - функция С, ее параметр также является указателем на функцию С
extern "С" void f1(void(*)(int));
Это объявление свидетельствует о том, что f1() является функцией языка С, которая не возвращает никаких значений. Она имеет один параметр в виде указателя на функцию, которая ничего не возвращает и получает один параметр типа int. Эта директива компоновки применяется как к самой функции f1(), так и к указателю на нее. Когда происходит вызов функции f1(), ей необходимо передать имя функции С или указатель на нее.
Поскольку директива компоновки применяется ко всем функциям в объявлении, для передачи функции С++ указателя на функцию С необходимо использовать определение типа (см. раздел 2.5.1):
// FC - указатель на функцию С
extern "С" typedef void FC(int);
// f2 - функция С++, параметром которой является указатель на функцию С
void f2(FC *);
Экспорт функций, созданных на языке С++, в другой язык
Используя директиву компоновки в определении функции, написанной на языке С++, эту функцию можно сделать доступной для программы, написанной на другом языке.
// функция calc() может быть вызвана из программы на языке С
extern "С" double calc(double dparm) { /* ... */ }
Код, создаваемый компилятором для этой функции, будет соответствовать указанному языку.
Следует заметить, что типы параметров и возвращаемого значения в функциях для разных языков зачастую ограничены. Например, почти наверняка нельзя написать функцию, которая передает объекты нетривиального класса С++ программе на языке С. Программа С не будет знать о конструкторах, деструкторах или других специфических для класса операциях.
Поддержка препроцессора при компоновке на языке С
Чтобы позволить компилировать тот же файл исходного кода на языке С или С++, при компиляции на языке С++ препроцессор автоматически определяет имя __cplusplus (два символа подчеркивания). Используя эту переменную, при компиляции на С++ можно условно включить код, компилируемый только на С++:
#ifdef __cplusplus
// ok: компилируется только в С++
extern "С"
#endif
int strcmp(const char*, const char*);
Перегруженные функции и директивы компоновки
Взаимодействие директив компоновки и перегрузки функций зависит от конкретного языка. Если язык поддерживает перегрузку функций, то компилятор, обрабатывая директивы компоновки для того языка, вероятней всего, выполнит ее.
Язык С не поддерживает перегрузку функций, поэтому нет ничего удивительного в том, что директива компоновки языка С может быть определена только для одной из функций в наборе перегруженных функций:
// ошибка: в директиве extern "С" указаны две одноименные функции
extern "С" void print(const char*);
extern "С" void print(int);
Если одна из функций в наборе перегруженных функций является функцией языка С, все остальные функции должны быть функциями С++:
class SmallInt { /* ... */ };
class BigNum { /* ... */ };
// функция С может быть вызвана из программ С и С++
// версия функции С++, перегружающая предыдущую функцию, может быть
// вызвана только из программ на языке С++
extern "С" double calc(double);
extern SmallInt calc(const SmallInt&);
extern BigNum calc(const BigNum&);
Версия функции calc() для языка С может быть вызвана как из программ на языке С, так и из программ на языке С++. Дополнительные функции с параметрами типа класса могут быть вызваны только из программ на языке С++, причем порядок объявления не имеет значения.
Упражнения раздела 19.8.3
Упражнение 19.26. Объясните эти объявления и укажите, допустимы ли они:
extern "С" int compute(int *, int);
extern "С" double compute(double *, double);
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОК