Взаимодействие с языком C
Ада позволяет нам взаимодействовать с кодом на многих языках, включая C и C++. В этом разделе обсуждается, как взаимодействовать с C.
Многоязычный проект
По умолчанию при использовании gprbuild мы компилируем только
исходные файлы Ада. Чтобы скомпилировать файлы C, нам нужно изменить файл
проекта, используемый gprbuild. Мы добавляем запись
Languages
, как в следующем примере:
project Multilang is
for Languages use ("ada", "c");
for Source_Dirs use ("src");
for Main use ("main.adb");
for Object_Dir use "obj";
end Multilang;
Соглашение о типах
Для взаимодействия с типами данных, объявленными в приложении на C,
необходимо указать аспект Convention
в соответствующем объявлении
типа Ада. В следующем примере вводим перечисленимый тип
C_Enum
, для соответствующего типа в исходном файле C:
Для взаимодействия со встроенными типами C используется пакет
Interfaces.C
, содержащий большинство необходимых определений
типов. Например:
Здесь мы взаимодействуем со структурой C (C_Struct
) и используем
соответствующие типы данных C (int
, long
, unsigned
и
double
). А вот объявление в C:
Подпрограммы на других языках
Вызов подпрограмм C из Ады
Мы используем аналогичный подход при взаимодействии с подпрограммами, написанными на C. Рассмотрим следующее объявление в заголовочном файле C:
Вот соответствующее определение функции на C:
Мы можем связать этот код с кодом на Аде, указывая аспект Import
.
Например:
При необходимости можно использовать другое имя подпрограммы в
Ада коде. Например, можно назвать функцию Get_Value
:
Вызов Ада подпрограмм из C
Вы также можете вызывать Ада подпрограммы из C приложений. Это делается
с помощью аспекта Export
. Например:
Вот соответствующее тело пакета с реализфцией этой функции:
На стороне C мы делаем так, как если бы функция была
написана на C: просто объявляем ее с помощью ключевого слова extern
.
Например:
Внешние переменные
Использование глобальных переменных C в Аде
Чтобы использовать глобальные переменные из C, мы используем тот
же метод, что и для подпрограмм: мы указываем аспекты Import
и
Convention
для каждой переменной, которую мы хотим импортировать.
Давайте воспользуемся примером из предыдущего раздела. Мы
добавим глобальную переменную (func_cnt
) для подсчета количества вызовов
функции (my_func
):
Переменная объявлена в файле C и увеличивается в my_func
:
В коде на Аде мы просто ссылаемся на внешнюю переменную:
Как мы видим, запустив приложение, значение счетчика - это количество
вызовов my_func
.
Мы можем использовать аспект External_Name
, если хотим сослаться
на имя отличное от наименования переменной в Аде, как мы это делали для
подпрограмм.
Использование переменных Ада в C
Вы также можете использовать переменные, объявленные в файлах Ада, в
приложениях C. Точно так же, как мы делали для подпрограмм, вы делаете
это с помощью аспекта Export
.
Давайте повторно воспользуемся прошлым примером и добавим счетчик, аналогично предыдущему примеру, но на этот раз будем увеличивать счетчик в Ада коде:
Затем переменная увеличивается в My_Func
:
В приложении C нам просто нужно объявить переменную и использовать ее:
Опять же, запустив приложение, мы видим, что значение счетчика - это
количество вызовов my_func
.
Автоматическое создание связок
В приведенных выше примерах мы вручную добавили аспекты в наш код Ада,
чтобы они соответствовали исходному коду C, с которым мы
взаимодействуем. Это называется созданием cвязки. Мы можем
автоматизировать этот процесс, используя особый ключ компилятора для
дампа спецификации Ада: -fdump-ada-spec
. Мы проиллюстрируем
это, вернувшись к нашему предыдущему примеру.
Это был наш заголовочный файл C:
Чтобы создать связку на Аде, мы вызовем компилятор следующим образом:
gcc -c -fdump-ada-spec -C ./test.h
Результатом является файл спецификации Ада с именем test_h.ads
:
Теперь мы просто ссылаемся на этот пакет test_h
в нашем приложении Ада:
Вы можете указать имя родительского модуля создаваемых связок в
качестве операнда для fdump-ada-spec
:
gcc -c -fdump-ada-spec -fada-spec-parent=Ext_C_Code -C ./test.h
И получим файл ext_c_code-test_h.ads
:
Адаптация связок
Компилятор делает все возможное при создании связок для файла
заголовка C. Однако мы получаем достаточно хороший результат и
сгенерированные связки не всегда соответствуют нашим ожиданиям.
Например, так может произойти при создании связок для функций,
которые имеют указатели в качестве аргументов. В этом случае
компилятор может использовать System.Address
как тип одного
или нескольких указателей. Хотя этот подход работает нормально
(как мы увидим позже),
обычно человек не так интерпретирует заголовочный файл C. Следующий
пример иллюстрирует эту проблему.
Начнем с такого заголовочного файла C:
И соответствующей реализация на C:
Далее мы создадим наши связки:
gcc -c -fdump-ada-spec -C ./test.h
Это создает следующую спецификацию в test_h.ads
:
Как мы видим, генератор связки полностью игнорирует объявление
struct test
, и
все ссылки на структуру test
заменяются адресами
(System.Address
). Тем не менее, эти
связки достаточно хороши, чтобы позволить нам создать тестовое
приложение на Ада:
Мы можем успешно собрать такую программу, используя автоматически сгенерированные связки, но они не идеальны. Вместо этого мы предпочли бы связки на Аде, которые соответствуют нашей (человеческой) интерпретации файла заголовка C. Это требует ручного анализа файла заголовка. Хорошая новость заключается в том, что мы можем использовать автоматически сгенерированные связки в качестве отправной точки и адаптировать их к нашим потребностям. Например, мы можем:
Определить тип
Test
на основеSystem.Address
и использовать его во всех соответствующих функциях.Удалить префикс
test_
во всех операциях с типомTest
.
Вот итоговая версия спецификации:
И это соответствующее тело на Аде:
Теперь мы можем использовать тип Test
и его операции в понятной и
удобочитаемой форме.