W Swift 6.3 lub nowszym oznacz funkcję globalną atrybutem @c, wygeneruj jej nagłówek zgodności, skompiluj kod wywołujący jako C i połącz całość za pomocą swiftc. W tym przewodniku zbudujemy niewielki natywny program wiersza poleceń dla Maca, który pokaże każdy z tych etapów.

Zadanie dla czytelnika jest konkretne: wywołać funkcję obliczeniową w Swift ze zwykłego pliku .c bez kompilowania go jako Objective-C ani C++. Oficjalna dokumentacja wydania Swift wskazuje @c jako obsługiwaną funkcję współdziałania z C, wprowadzoną w Swift 6.3. Przed zastosowaniem starszych porad dotyczących eksperymentalnych atrybutów sprawdź zainstalowany kompilator. Wydanie Swift 6.3

Co sprawia, że funkcję Swift można wywołać z C?

Muszą być zgodne trzy elementy: deklaracja odczytywana przez kompilator C, konwencja wywołań używana przez implementację oraz pliki obiektowe przekazane konsolidatorowi. Nagłówek zapewnia pierwszy element; nie dostarcza brakującej implementacji ani nie zmienia ABI istniejącej funkcji.

Atrybut @c nadaje globalnej funkcji Swift konwencję wywołań C i ogranicza jej sygnaturę do typów reprezentowalnych w C. Opcjonalny argument atrybutu pozwala wybrać nazwę funkcji w C. Swift umieszcza odpowiadającą jej deklarację w sekcji zgodnej z C wygenerowanego nagłówka zgodności. Zasady te określa przyjęta propozycja Swift Evolution. Funkcje i wyliczenia zgodne z C

W tym przykładzie interfejs przyjmuje i zwraca liczbę całkowitą typu C. Przykładowe obliczenie ogranicza procent wyświetlany na ekranie do zakresu od zera do 100 włącznie. Nie ma tu buforów, alokowanych wyników ani okresów życia obiektów, które trzeba uzgadniać. Dzięki temu pierwsza kompilacja skupia się na kompilowaniu i konsolidowaniu.

Najpierw sprawdź łańcuch narzędzi na Macu

Potrzebujesz środowiska programistycznego macOS z Clangiem, SDK macOS i Swift 6.3 lub nowszym. Apple dostarcza narzędzia wiersza poleceń wraz z Xcode, a także oferuje osobny pakiet Command Line Tools. Jeśli masz już Xcode, Apple informuje, że osobna instalacja narzędzi nie jest potrzebna. Instalowanie narzędzi wiersza poleceń

Uruchom te polecenia w Terminalu:

xcode-select --print-path
xcrun --sdk macosx swiftc --version
xcrun --sdk macosx clang --version

Sprawdź wersję samego kompilatora Swift. Starszy kompilator nie zyskuje obsługi @c tylko dlatego, że zainstalowano nowsze SDK. Jeśli masz kilka wersji Xcode, Apple opisuje, jak wybrać aktywną wersję w Xcode > Settings > Locations > Command Line Tools albo zmienić ją na czas sesji powłoki za pomocą DEVELOPER_DIR. Konfigurowanie ustawień narzędzi wiersza poleceń

Poniższe polecenia używają xcrun, aby wywołać wybrane narzędzia programistyczne. Apple opisuje tę rolę w zakresie wyszukiwania narzędzi w dokumentacji dotyczącej kompilacji z wiersza poleceń. FAQ: kompilowanie z wiersza poleceń za pomocą Xcode

Utwórz pusty katalog roboczy i zapisz w nim oba pliki źródłowe pokazane poniżej. Polecenia kompilacji uruchamiaj z tego katalogu. To natywna kompilacja na lokalnym Macu; nie tworzy pliku uniwersalnego ani nie konfiguruje innej platformy.

Napisz implementację w Swift i kod wywołujący w C

Zdefiniuj eksportowaną funkcję w Swift

Zapisz ten kompletny plik źródłowy Swift jako Percent.swift:

@c(demo_clamp_percent)
public func clampPercent(_ value: CInt) -> CInt {
    if value < 0 {
        return 0
    }
    if value > 100 {
        return 100
    }
    return value
}

Nazwa w Swift to clampPercent; nazwa widoczna w C to demo_clamp_percent. CInt odpowiada typowi int w C. To jawne przyporządkowanie pozwala uniknąć założenia, że Swift Int oznacza C int: udokumentowanym odpowiednikiem Swift Int jest ptrdiff_t. Funkcje i wyliczenia zgodne z C

Funkcja porównuje wartości i zwraca jedną z wartości wejściowych albo stałą. Jej obliczenia nie powodują przepełnienia arytmetycznego, także dla liczb spoza zakresu procentowego. Prefiks eksportowanej nazwy to przykładowa konwencja, która pozwala odróżnić ten interfejs od innych funkcji w większym programie.

Wywołaj funkcję z kompletnego programu w C

Zapisz ten plik jako main.c. Podczas kompilacji zostanie utworzony dołączany nagłówek:

#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;
}

Trzy wartości wejściowe sprawdzają dolną granicę, niezmienioną wartość i górną granicę. Program jawnie sprawdza wyniki i zwraca niezerowy kod zakończenia w razie niezgodności, więc jego wynik jest bardziej użyteczny niż demonstracja, która tylko wyświetla liczbę.

Wygeneruj nagłówek, skompiluj kod C i połącz program

Uruchom tę sekwencję w powłoce z katalogu zawierającego oba pliki. Jeśli którekolwiek polecenie się nie powiedzie, sekwencja zostanie przerwana:

(
    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
)

Pierwsze polecenie tworzy plik obiektowy Swift i nagłówek zgodności. Sterownik Swifta definiuje -emit-object jako opcję generującą plik obiektowy, a -parse-as-library jako opcję analizującą kod źródłowy biblioteki zamiast skryptu. Dzięki temu punkt wejścia programu znajduje się w pliku C. Opcje sterownika kompilatora Swift

Mimo historycznej nazwy opcja -emit-objc-header-path generuje również sekcję zgodności z C w nagłówku dla deklaracji @c. Przyjęta propozycja wyraźnie zachowuje tę flagę do tego celu. Nie zastępuj ręcznie wygenerowanego nagłówka domniemaną deklaracją funkcji. Funkcje i wyliczenia zgodne z C

Drugie polecenie jest kluczowym sprawdzianem języka źródłowego. Dokumentacja Clanga opisuje -x jako opcję wyboru języka pliku wejściowego, a -c jako opcję generującą plik obiektowy bez konsolidowania. Tutaj -x c -std=c11 wymusza kompilację kodu wywołującego jako C11 przy użyciu rzeczywiście wygenerowanego nagłówka. Przewodnik po poleceniach Clanga

Ostatnie wywołanie kompilatora konsoliduje oba pliki obiektowe za pomocą sterownika Swifta. Własny przewodnik kompilatora Swift zaleca zlecenie konsolidacji plików narzędziu swiftc zamiast ręcznego konstruowania polecenia niższego poziomu. Debugowanie kompilatora

Oczekiwany wynik, zgodny z gałęziami w przykładzie:

-12 -> 0
42 -> 42
140 -> 100

Polecenia te sprawdzono na podstawie pobranej dokumentacji, ale nie zostały tutaj wykonane: środowisko badawcze działa pod Linuksem i nie ma dostępnego ani swiftc, ani Clanga. Powyższy wynik jest oczekiwany, a nie pochodzi z testu uruchomionego na Macu.

Wybierz interfejs pasujący do istniejącego kodu

W przypadku nowego, niewielkiego interfejsu i Swifta 6.3 lub nowszego opisane wyżej podejście z generowanym nagłówkiem bezpośrednio wiąże deklarację z implementacją. Dokumentacja wydania obsługuje też drugi kierunek: @c @implementation implementuje funkcję zadeklarowaną wcześniej w zaimportowanym nagłówku C, a Swift sprawdza zgodność sygnatur. To przydatne, gdy istniejące API w C musi pozostać kontraktem interfejsu. Wydanie Swift 6.3

Jeśli otaczający projekt wywołuje już kod Swift przez C++, inną możliwością są generowane przez Swift wiązania C++. Publiczne funkcje Swift są reprezentowane przez wbudowane funkcje C++ w przestrzeni nazw modułu. Kod wywołujący w C potrzebowałby osobnego adaptera skompilowanego jako C++, który udostępnia niewielką deklarację przeznaczoną dla C. Jednostka translacji C dołączałaby nagłówek C tego adaptera. Łączenie Swift i C++

W takim adapterze extern \"C\" określa powiązanie językowe C dla deklaracji lub definicji w C++. Nie zmienia składni zawartości nagłówka C++ na składnię C. Projekt roboczy standardu C++ definiuje specyfikacje powiązań i rozróżnia powiązanie językowe od zwykłych deklaracji. Projekt roboczy C++: specyfikacje powiązań

W tym przewodniku bezpośrednią, obsługiwaną drogą jest aktualizacja starszego kompilatora do wersji obsługującej @c. Propozycja opisuje @_cdecl jako eksperymentalnego poprzednika; użycie tego starszego zapisu w nowym przykładzie zacierałoby istotną granicę wersji. Funkcje i wyliczenia zgodne z C

Diagnozuj błędy na etapie, na którym występują

Do zawężenia poszukiwań użyj pierwszego kroku kompilacji, który kończy się niepowodzeniem. Błąd generowania nagłówka, błąd parsera C i niezdefiniowany symbol wymagają różnych poprawek.

Pierwszy błąd Co sprawdzić w następnej kolejności
Swift odrzuca @c Sprawdź ponownie wybraną wersję kompilatora Swift; ten przykład wymaga Swift 6.3 lub nowszego.
C nie może znaleźć Percent-Swift.h Upewnij się, że polecenie Swifta zakończyło się powodzeniem i oba pliki są kompilowane z właściwego katalogu.
C nie może znaleźć demo_clamp_percent Sprawdź nowo wygenerowany nagłówek i zapis atrybutu; po zmianach w kodzie źródłowym ponów kompilację.
C zgłasza składnię C++ lub Objective-C Sprawdź, który nagłówek został dołączony i który łańcuch narzędzi go wygenerował. Podczas poszukiwania przyczyny zachowaj jawne wymuszenie kompilacji jako C.
Konsolidator nie może rozwiązać symbolu eksportowanej funkcji Sprawdź, czy Percent.o znajduje się w końcowym poleceniu i czy został ponownie wygenerowany z aktualnego kodu Swift.
Program zgłasza nieoczekiwane wyniki Odczytaj wydrukowaną parę wejście/wynik i porównaj ją z gałęziami w kodzie Swift.

Ta sekwencja odpowiada etapom kompilatora opisanym w przewodniku Clanga: analiza składniowa ustala, czy kod źródłowy jest poprawny, generowanie pliku obiektowego tworzy skompilowany kod, a konsolidacja łączy skompilowane dane wejściowe. Samo pomyślne skompilowanie kodu C potwierdza więc wykonanie tylko części zadania. Przewodnik po poleceniach Clanga

Dostosowując przykład, utrzymuj mały interfejs funkcji dostępnej w C i określaj jawnie kontrakt każdego nowego parametru. W przypadku tekstu ustal kodowanie i sposób obsługi długości; w przypadku buforów ustal ich pojemność i zasady własności przed napisaniem opakowania. To decyzje projektowe dotyczące interfejsu, które wykraczają poza tę demonstrację operującą wyłącznie na liczbach całkowitych.

Ukończona lokalna kontrola daje trzy obserwowalne wyniki: wygenerowany nagłówek jest akceptowany podczas jawnej kompilacji C, obie implementacje są łączone w jeden plik wykonywalny, a kod wywołujący sprawdza wybrane wartości wejściowe. Zachowaj te kontrole, gdy zastąpisz przykładowe obliczenie własną funkcją Swift.

Źródła