В Swift 6.3 или более поздней версии пометьте глобальную функцию атрибутом @c, сгенерируйте заголовочный файл совместимости, скомпилируйте вызывающий код как C и выполните компоновку с помощью swiftc. В этом пошаговом руководстве создаётся небольшая нативная программа командной строки для Mac, демонстрирующая каждый этап.
Задача читателя конкретна: вызвать вычисление на Swift из обычного файла .c, не компилируя его как Objective-C или C++. В официальной документации к выпуску Swift говорится, что @c — это поддерживаемая возможность взаимодействия с C, представленная в Swift 6.3. Прежде чем следовать более старым советам об экспериментальных атрибутах, проверьте установленный компилятор. Выпуск Swift 6.3
Что позволяет вызывать функцию Swift из C?
Должны согласоваться три элемента: объявление, которое читает компилятор C, соглашение о вызовах, используемое реализацией, и объектные файлы, передаваемые компоновщику. Заголовочный файл решает первую задачу; он не может предоставить отсутствующую реализацию или изменить ABI существующей функции.
Атрибут @c задаёт глобальной функции Swift соглашение о вызовах C и ограничивает её сигнатуру типами, представимыми в C. Необязательный аргумент атрибута задаёт имя функции в C. Swift помещает соответствующее объявление в часть сгенерированного заголовочного файла совместимости, совместимую с C. Эти правила описаны в принятом предложении Swift Evolution. Функции и перечисления, совместимые с C
В этом примере интерфейс принимает целое число C и возвращает целое число C. Условный расчёт ограничивает процент отображения включительным диапазоном от нуля до 100. Здесь нет буферов, выделяемых результатов или времени жизни объектов, которые нужно согласовывать. Благодаря этому первая сборка сосредоточена на компиляции и компоновке.
Сначала проверьте инструментарий Mac
Вам нужна среда разработки macOS с Clang, SDK macOS и Swift 6.3 или более поздней версии. Apple предоставляет инструменты командной строки вместе с Xcode, а также предлагает отдельный пакет Command Line Tools. Если Xcode уже установлен, отдельная установка инструментов не требуется. Установка инструментов командной строки
Выполните эти проверки в Терминале:
xcode-select --print-path
xcrun --sdk macosx swiftc --version
xcrun --sdk macosx clang --version
Проверьте версию самого компилятора Swift. Установка более нового SDK не добавит атрибут @c в старый компилятор. Если установлено несколько версий Xcode, Apple предлагает выбрать активную версию в Xcode > Settings > Locations > Command Line Tools или переопределить её для сеанса оболочки с помощью DEVELOPER_DIR. Настройка параметров инструментов командной строки
В приведённых ниже командах используется xcrun для вызова выбранных инструментов разработки. Apple описывает эту роль поиска инструментов в справочнике по сборке из командной строки. Часто задаваемые вопросы о сборке из командной строки с помощью Xcode
Создайте пустой рабочий каталог и сохраните в нём оба исходных файла, приведённых ниже. Выполняйте команды сборки из этого каталога. Это нативная сборка для текущего Mac; она не создаёт универсальный бинарный файл и не настраивается для другой платформы.
Напишите реализацию на Swift и вызывающий код на C
Определите экспортируемую функцию Swift
Сохраните этот полный исходный файл Swift под именем Percent.swift:
@c(demo_clamp_percent)
public func clampPercent(_ value: CInt) -> CInt {
if value < 0 {
return 0
}
if value > 100 {
return 100
}
return value
}
Имя функции в Swift — clampPercent; имя для C — demo_clamp_percent. Тип CInt соответствует типу int в C. Такое явное соответствие позволяет не предполагать, что Swift Int означает C int: документированное соответствие для Swift Int — ptrdiff_t. Функции и перечисления, совместимые с C
Функция сравнивает значения и возвращает либо одно из входных значений, либо константу. В её вычислениях нет арифметического переполнения, в том числе для значений за пределами диапазона процентов. Префикс в экспортируемом имени — это пример соглашения, позволяющего отличать этот интерфейс от других функций в более крупной программе.
Вызовите её из полной программы на C
Сохраните этот файл под именем main.c. Во время сборки будет создан подключаемый заголовочный файл:
#include <stddef.h>
#include <stdio.h>
#include "Percent-Swift.h"
int main(void) {
const int inputs[] = {-12, 42, 140};
const int expected[] = {0, 42, 100};
const size_t count = sizeof inputs / sizeof inputs[0];
for (size_t i = 0; i < count; ++i) {
const int actual = demo_clamp_percent(inputs[i]);
printf("%d -> %d\n", inputs[i], actual);
if (actual != expected[i]) {
fprintf(stderr, "Unexpected result at index %zu\n", i);
return 1;
}
}
return 0;
}
Три входных значения проверяют нижнюю границу, неизменённое значение и верхнюю границу. Программа явно проверяет результаты и возвращает ненулевой код при несовпадении, поэтому её вывод информативнее, чем у демонстрации, которая просто печатает число.
Сгенерируйте заголовочный файл, скомпилируйте C и выполните компоновку программы
Выполните эту последовательность в оболочке из каталога, где находятся оба файла. Если какой-либо шаг завершится ошибкой, последовательность остановится:
(
set -eu
SDK_PATH="$(xcrun --sdk macosx --show-sdk-path)"
xcrun --sdk macosx swiftc \
-sdk "$SDK_PATH" \
-parse-as-library \
-module-name Percent \
-emit-object Percent.swift \
-emit-objc-header-path Percent-Swift.h \
-o Percent.o
xcrun --sdk macosx clang \
-isysroot "$SDK_PATH" \
-x c -std=c11 \
-Wall -Wextra -Werror \
-c main.c -o main.o
xcrun --sdk macosx swiftc \
-sdk "$SDK_PATH" \
main.o Percent.o \
-o percent-demo
./percent-demo
)
Первая команда создаёт объектный файл Swift и заголовочный файл совместимости. Драйвер Swift определяет -emit-object как вывод объектного файла, а -parse-as-library — как разбор исходного файла библиотеки, а не скрипта. Благодаря этому точка входа исполняемого файла остаётся в файле C. Параметры драйвера компилятора Swift
Несмотря на историческое название, параметр -emit-objc-header-path также запрашивает раздел заголовочного файла совместимости на C для объявлений @c. В принятом предложении прямо указано, что этот флаг сохраняется и для такой цели. Не заменяйте сгенерированный заголовочный файл вручную предполагаемым прототипом. Функции и перечисления, совместимые с C
Вторая команда служит решающей проверкой языка исходного файла. В документации Clang указано, что -x выбирает язык входного файла, а -c создаёт объектный файл без компоновки. Здесь -x c -std=c11 заставляет компилировать вызывающий код как C11, используя настоящий сгенерированный заголовочный файл. Руководство по командам Clang
Последний вызов компилятора выполняет компоновку обоих объектных файлов с помощью драйвера Swift. В руководстве компилятора Swift рекомендуется поручать компоновку файлов инструменту swiftc, а не собирать команду низкоуровневой компоновки вручную. Отладка компилятора
Ожидаемый вывод, полученный из ветвей примера:
-12 -> 0
42 -> 42
140 -> 100
Эти команды проверены по найденной документации, но здесь не выполнялись: среда исследования работает под Linux, в ней нет ни swiftc, ни Clang. Приведённый выше вывод — ожидаемый результат, а не отчёт о запуске на Mac.
Выберите интерфейс, подходящий к существующему коду
Для нового небольшого интерфейса на Swift 6.3 или более поздней версии подход с генерируемым заголовочным файлом позволяет связать объявление непосредственно с реализацией. В документации к выпуску также описано обратное направление: @c @implementation реализует функцию, уже объявленную в импортированном заголовочном файле C, а Swift проверяет совпадение сигнатур. Это полезно, когда интерфейс существующего API на C должен остаться контрактом. Выпуск Swift 6.3
Если окружающий проект уже вызывает Swift через C++, можно использовать и сгенерированные Swift привязки C++. Общедоступные функции Swift представлены встроенными функциями C++ внутри пространства имён модуля. Вызывающему коду на C понадобится отдельный адаптер, скомпилированный как C++, который предоставит небольшое объявление для C. Единица трансляции на C будет подключать заголовочный файл этого адаптера для C. Совместное использование Swift и C++
В таком адаптере extern "C" задаёт связь языка C для объявления или определения на C++. Он не превращает содержимое заголовочного файла C++ в синтаксис C. В проекте стандарта C++ определены спецификации связи и объяснено различие между связью языка и обычными объявлениями. Проект стандарта C++: спецификации связи
Для этого руководства прямой поддерживаемый путь — обновить старый компилятор до версии с поддержкой @c. В предложении @_cdecl описан как экспериментальный предшественник; использование этого старого написания в новом примере скрыло бы важную границу между версиями. Функции и перечисления, совместимые с C
Диагностируйте сбои на соответствующем этапе
Чтобы сузить поиск причины, ориентируйтесь на первый завершившийся ошибкой шаг сборки. Сбой генерации заголовочного файла, ошибка разбора C и неразрешённый символ требуют разных исправлений.
| Первый сбой | Что проверить дальше |
|---|---|
Swift отвергает @c |
Ещё раз проверьте выбранную версию компилятора Swift: для этого примера требуется Swift 6.3 или более поздняя версия. |
C не может найти Percent-Swift.h |
Убедитесь, что команда Swift завершилась успешно и оба файла собираются из нужного каталога. |
C не может найти demo_clamp_percent |
Проверьте вновь сгенерированный заголовочный файл и написание атрибута; после изменений исходного кода выполните сборку повторно. |
| C сообщает о синтаксисе C++ или Objective-C | Проверьте, какой заголовочный файл был подключён и каким набором инструментов он сгенерирован. Во время диагностики сохраняйте явную компиляцию как C. |
| При компоновке не удаётся разрешить экспортированную функцию | Убедитесь, что Percent.o включён в последнюю команду и пересобран из текущего исходного файла Swift. |
| Исполняемый файл выдаёт неожиданные результаты | Сопоставьте напечатанную пару «входное значение/результат» с ветвями Swift. |
Эта последовательность соответствует этапам компилятора, описанным в документации Clang: разбор устанавливает, корректен ли исходный код, генерация объектов создаёт скомпилированный код, а компоновка объединяет скомпилированные входные файлы. Поэтому успешная компиляция C подтверждает лишь часть результата. Руководство по командам Clang
При адаптации примера оставьте функцию для C небольшой и явно определите контракт каждого нового параметра. Для текста решите, как обрабатывать кодировку и длину; для буферов заранее определите ёмкость и владение. Это вопросы проектирования интерфейса, выходящие за рамки этой демонстрации только с целыми числами.
Готовая локальная проверка даёт три наблюдаемых результата: сгенерированный заголовочный файл успешно используется при явной компиляции C, обе реализации компонуются в один исполняемый файл, а вызывающий код проверяет выбранные входные значения. Сохраните эти проверки, когда замените условный расчёт собственной функцией Swift.
Источники
- Выпуск Swift 6.3 — Swift.org
- Функции и перечисления, совместимые с C — Swift Evolution
- Установка инструментов командной строки — Apple
- Настройка параметров инструментов командной строки — Apple
- Часто задаваемые вопросы о сборке из командной строки с помощью Xcode — Apple
- Параметры драйвера компилятора Swift — Проект Swift
- Руководство по командам Clang — Проект LLVM
- Отладка компилятора — Проект Swift
- Совместное использование Swift и C++ — Swift.org
- Проект стандарта C++: спецификации связи — Проект стандарта C++