Markeer met Swift 6.3 of hoger een globale functie met @c, genereer de compatibiliteitsheader, compileer de aanroepende code als C en koppel met swiftc. In deze walkthrough bouw je een klein native Mac-command-lineprogramma dat elke grens demonstreert.

De taak voor de lezer is specifiek: roep een Swift-berekening aan vanuit een gewoon .c-bestand zonder dat bestand als Objective-C of C++ te compileren. De officiële releasdocumentatie van Swift noemt @c als de ondersteunde functie voor C-interoperabiliteit die in Swift 6.3 is geïntroduceerd. Controleer welke compiler je hebt geïnstalleerd voordat je oudere adviezen over experimentele attributen volgt. Swift 6.3 uitgebracht

Waardoor is een Swift-functie aanroepbaar vanuit C?

Drie onderdelen moeten op elkaar aansluiten: de declaratie die de C-compiler leest, de aanroepconventie van de implementatie en de objectbestanden die aan de linker worden aangeleverd. Een header regelt het eerste onderdeel; hij kan geen ontbrekende implementatie leveren en geen bestaande ABI wijzigen.

Het attribuut @c geeft een globale Swift-functie de C-aanroepconventie en beperkt de signatuur tot typen die in C kunnen worden weergegeven. Een optioneel attribuutargument kiest de C-naam. Swift neemt de bijbehorende declaratie op in het C-compatibele gedeelte van de gegenereerde compatibiliteitsheader. Deze werking is vastgelegd in het geïmplementeerde Swift Evolution-voorstel. C-compatibele functies en enums

In dit voorbeeld accepteert en retourneert de interface een C-integer. De fictieve berekening begrenst een weergavepercentage tot het inclusieve bereik van nul tot en met 100. Er zijn geen buffers, toegewezen resultaten of objectlevensduren die op elkaar moeten worden afgestemd. Zo blijft de eerste build gericht op compileren en linken.

Controleer eerst de Mac-toolchain

Je hebt een macOS-ontwikkelomgeving nodig met Clang, een macOS-SDK en Swift 6.3 of hoger. Apple levert opdrachtregelprogramma's bij Xcode en biedt ook een afzonderlijk pakket met Command Line Tools aan. Als je Xcode al hebt, is volgens Apple een afzonderlijke installatie van de tools niet nodig. De opdrachtregelprogramma's installeren

Voer deze controles uit in Terminal:

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

Controleer de versie van de Swift-compiler zelf. Een oudere compiler krijgt de functie @c niet alleen doordat er een nieuwere SDK is geïnstalleerd. Als er meerdere versies van Xcode aanwezig zijn, documenteert Apple hoe je de actieve versie selecteert via Xcode > Settings > Locations > Command Line Tools, of hoe je die voor een shellsessie overschrijft met DEVELOPER_DIR. Instellingen voor opdrachtregelprogramma's configureren

De onderstaande opdrachten gebruiken xcrun om de geselecteerde ontwikkeltools aan te roepen. Apple beschrijft deze rol voor het vinden van tools in de naslaggids voor bouwen via de opdrachtregel. FAQ over bouwen via de opdrachtregel met Xcode

Maak een lege werkmap en sla beide bronbestanden hieronder daarin op. Voer de buildopdrachten vanuit die map uit. Dit is een native build voor de lokale Mac; er wordt geen universeel binair bestand gemaakt en er wordt geen ander platform geconfigureerd.

Schrijf de Swift-implementatie en de C-aanroepende code

Definieer de geëxporteerde Swift-functie

Sla dit volledige Swift-bronbestand op als Percent.swift:

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

De Swift-naam is clampPercent; de naam die C gebruikt is demo_clamp_percent. CInt komt overeen met int in C. Deze expliciete koppeling voorkomt dat je ervan uitgaat dat Swift Int hetzelfde betekent als C int: de gedocumenteerde koppeling voor Swift Int is ptrdiff_t. C-compatibele functies en enums

De functie vergelijkt waarden en retourneert een invoerwaarde of een constante. De berekening kan geen rekenkundige overflow veroorzaken, ook niet bij invoerwaarden buiten het percentagebereik. Het voorvoegsel in de geëxporteerde naam is een voorbeeldconventie waarmee je deze interface kunt onderscheiden van andere functies in een groter programma.

Roep de functie aan vanuit een volledig C-programma

Sla dit bestand op als main.c. Tijdens het bouwen wordt de opgenomen header aangemaakt:

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

De drie invoerwaarden testen de ondergrens, een ongewijzigde waarde en de bovengrens. Het programma controleert de resultaten expliciet en retourneert een niet-nulstatus als er iets niet klopt. Daardoor is de uitvoer nuttiger dan bij een demonstratie die alleen een getal afdrukt.

Genereer de header, compileer C en koppel het programma

Voer deze reeks in een shell uit vanuit de map met de twee bestanden. Bij een fout stopt de reeks na de betreffende buildstap:

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

De eerste opdracht maakt het Swift-objectbestand en de compatibiliteitsheader. De Swift-driver definieert -emit-object als uitvoer van een objectbestand en -parse-as-library als het parseren van bibliotheekbroncode in plaats van een script. Zo blijft het C-bestand verantwoordelijk voor het toegangspunt van het uitvoerbare bestand. Opties van de Swift-compilerdriver

Ondanks de historische naam vraagt -emit-objc-header-path ook om het C-gedeelte van de compatibiliteitsheader voor declaraties met @c. Het geïmplementeerde voorstel behoudt deze vlag expliciet voor dit doel. Vervang de gegenereerde header niet handmatig door een veronderstelde prototype-declaratie. C-compatibele functies en enums

De tweede opdracht is de beslissende controle van de brontaal. Clang documenteert -x als de keuze van de invoertaal en -c als het maken van een objectbestand zonder te linken. Hier zorgt -x c -std=c11 ervoor dat de aanroepende code als C11 wordt gecompileerd, terwijl de daadwerkelijk gegenereerde header wordt gelezen. Clang-opdrachtgids

De laatste aanroep van de compiler linkt beide objectbestanden met de Swift-driver. In de eigen compilergids raadt Swift aan swiftc de bestanden te laten linken in plaats van handmatig een linkopdracht op lager niveau samen te stellen. De compiler debuggen

De verwachte uitvoer, afgeleid van de vertakkingen in het voorbeeld, is:

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

Deze opdrachten zijn gecontroleerd aan de hand van de geraadpleegde documentatie, maar zijn hier niet uitgevoerd: de onderzoeksomgeving draait op Linux en heeft noch swiftc noch Clang beschikbaar. De bovenstaande uitvoer is een verwacht resultaat, geen gerapporteerde Mac-test.

Kies de interface die bij je bestaande code past

Voor een nieuwe, kleine interface met Swift 6.3 of hoger houdt de hierboven beschreven aanpak met een gegenereerde header de declaratie rechtstreeks gekoppeld aan de implementatie. De releasedocumentatie ondersteunt ook een tweede richting: @c @implementation implementeert een functie die al in een geïmporteerde C-header is gedeclareerd, waarbij Swift controleert of de signaturen overeenkomen. Dat is handig als een bestaande C-API de interfaceovereenkomst moet blijven bepalen. Swift 6.3 uitgebracht

Als het omliggende project Swift al via C++ aanroept, bieden de gegenereerde C++-bindingen van Swift een andere route. Publieke Swift-functies worden weergegeven als inline C++-functies binnen een modulenamespace. Een C-aanroepende code heeft dan een afzonderlijke adapter nodig die als C++ wordt gecompileerd en een kleine C-declaratie beschikbaar stelt. De C-vertaaleenheid neemt de C-header van die adapter op. Swift en C++ combineren

In zo'n adapter geeft extern \"C\" C-taalkoppeling aan de C++-declaratie of -definitie. Het zet de inhoud van een C++-header niet om naar C-syntaxis. De C++-werkconcepttekst definieert koppelingsspecificaties en maakt onderscheid tussen taalkoppeling en gewone declaraties. C++-werkconcepttekst: koppelingsspecificaties

Voor deze handleiding is het bijwerken van een oudere compiler naar een versie die @c ondersteunt de rechtstreekse, ondersteunde aanpak. Het voorstel beschrijft @_cdecl als de experimentele voorganger; die oudere schrijfwijze overnemen in een nieuw voorbeeld zou een betekenisvolle versiegrens verhullen. C-compatibele functies en enums

Diagnoseer fouten bij de stap waar ze optreden

Gebruik de eerste mislukte buildstap om het onderzoek af te bakenen. Een fout bij het genereren van de header, een parserfout in C en een niet-opgelost symbool vereisen elk een andere oplossing.

Eerste fout Volgende controle
Swift weigert @c Controleer opnieuw de versie van de geselecteerde Swift-compiler; dit voorbeeld vereist Swift 6.3 of hoger.
C kan Percent-Swift.h niet vinden Controleer of de Swift-opdracht is geslaagd en of beide bestanden vanuit de bedoelde map worden gebouwd.
C kan demo_clamp_percent niet vinden Bekijk de zojuist gegenereerde header en controleer de schrijfwijze van het attribuut; bouw opnieuw na broncodewijzigingen.
C meldt C++- of Objective-C-syntaxis Controleer welke header is opgenomen en welke toolchain die heeft gegenereerd. Behoud expliciete C-compilatie tijdens het onderzoek.
De linker kan de geëxporteerde functie niet oplossen Controleer of Percent.o in de laatste opdracht is opgenomen en opnieuw is gebouwd op basis van de huidige Swift-broncode.
Het uitvoerbare bestand geeft onverwachte resultaten Lees het afgedrukte paar invoer/resultaat en vergelijk het met de vertakkingen in Swift.

Deze reeks volgt de compilerfasen die door Clang zijn gedocumenteerd: bij het parseren wordt vastgesteld of de broncode geldig is, bij objectgeneratie wordt gecompileerde code gemaakt en bij het linken worden de gecompileerde invoerbestanden gecombineerd. Een geslaagde C-compilatie vormt dus maar een deel van de taak. Clang-opdrachtgids

Houd bij het aanpassen van het voorbeeld de C-interface klein en leg voor elke nieuwe parameter expliciet vast wat die betekent. Bepaal voor tekst de codering en lengteafhandeling; bepaal voor buffers de capaciteit en het eigenaarschap voordat je de wrapper schrijft. Dat zijn ontwerpbeslissingen voor de interface die buiten deze demonstratie met alleen integers vallen.

De voltooide lokale controle levert drie waarneembare resultaten op: de gegenereerde header wordt geaccepteerd tijdens expliciete C-compilatie, beide implementaties worden tot één uitvoerbaar bestand gelinkt en de aanroepende code controleert de gekozen invoerwaarden. Behoud deze controles wanneer je de fictieve berekening vervangt door je eigen Swift-functie.

Bronnen