Med Swift 6.3 eller senare markerar du en global funktion med @c, genererar dess kompatibilitetsheader, kompilerar anroparen som C och länkar med swiftc. Den här genomgången bygger ett litet inbyggt kommandoradsprogram för Mac som demonstrerar varje gränssnitt.

Uppgiften är tydligt avgränsad: anropa en Swift-beräkning från en vanlig .c-fil utan att kompilera filen som Objective-C eller C++. Swifts officiella versionsdokumentation anger @c som den funktion för C-interoperabilitet som introducerades i Swift 6.3. Kontrollera vilken kompilator du har installerad innan du följer äldre råd om experimentella attribut. Swift 6.3 Released

Vad krävs för att en Swift-funktion ska kunna anropas från C?

Tre delar måste stämma överens: deklarationen som C-kompilatorn läser, anropskonventionen som implementationen använder och objektfilerna som skickas till länkaren. En header hanterar den första delen; den kan inte tillhandahålla en implementation som saknas eller ändra ett befintligt funktions ABI.

Attributet @c ger en global Swift-funktion C:s anropskonvention och begränsar dess signatur till typer som kan representeras i C. Ett valfritt attributargument anger dess C-namn. Swift tar med motsvarande deklaration i den C-kompatibla delen av den genererade kompatibilitetsheadern. Dessa beteenden specificeras i det implementerade Swift Evolution-förslaget. C compatible functions and enums

I det här exemplet tar gränssnittet emot och returnerar ett C-heltal. Den syntetiska beräkningen begränsar en visningsprocent till det inklusiva intervallet från noll till 100. Det finns inga buffertar, allokerade resultat eller objektlivstider att samordna. Då kan den första bygget fokusera på kompilering och länkning.

Kontrollera Mac-verktygskedjan först

Du behöver en macOS-utvecklingsmiljö med Clang, en macOS SDK och Swift 6.3 eller senare. Apple tillhandahåller kommandoradsverktyg med Xcode och erbjuder även ett separat paket med Command Line Tools. Om du redan har Xcode säger Apple att en separat installation av verktygen inte behövs. Installing the command-line tools

Kör dessa kontroller i Terminal:

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

Kontrollera själva Swift-kompilatorns version. En äldre kompilator får inte funktionen @c bara för att en nyare SDK är installerad. Om flera Xcode-versioner finns installerade beskriver Apple hur du väljer den aktiva versionen under Xcode > Settings > Locations > Command Line Tools, eller åsidosätter den för en skalsession med DEVELOPER_DIR. Configuring command-line tools settings

Kommandona nedan använder xcrun för att anropa de valda utvecklingsverktygen. Apple beskriver denna roll för att hitta verktyg i sin referens för kommandoradsbyggen. Building from the Command Line with Xcode FAQ

Skapa en tom arbetskatalog och spara båda källfilerna nedan i den. Kör byggkommandona från katalogen. Detta är ett inbyggt bygge för den lokala Mac-datorn; det skapar inte en universalbinär och konfigurerar inte någon annan plattform.

Skriv Swift-implementationen och C-anroparen

Definiera den exporterade Swift-funktionen

Spara den här fullständiga Swift-källfilen som 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-namnet är clampPercent; namnet som används mot C är demo_clamp_percent. CInt motsvarar C:s int. Genom att använda denna uttryckliga koppling slipper du anta att Swift Int betyder C int: den dokumenterade motsvarigheten för Swift Int är ptrdiff_t. C compatible functions and enums

Funktionen jämför värden och returnerar antingen ett indata-värde eller en konstant. Beräkningen medför ingen aritmetisk överströmning, inte heller för indatavärden utanför procentintervallet. Prefixet i det exporterade namnet är ett exempel på en konvention för att skilja detta gränssnitt från andra funktioner i ett större program.

Anropa funktionen från ett komplett C-program

Spara filen som main.c. Bygget skapar den inkluderade headern:

#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 tre indatavärdena testar den nedre gränsen, ett oförändrat värde och den övre gränsen. Programmet kontrollerar resultaten uttryckligen och returnerar en status som inte är noll vid fel, så utdata är mer användbara än i en demonstration som bara skriver ut ett tal.

Generera headern, kompilera C och länka programmet

Kör följande sekvens i ett skal från katalogen som innehåller de två filerna. Varje byggsteg avbryter sekvensen om det misslyckas:

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

Det första kommandot skapar Swift-objektfilen och kompatibilitetsheadern. Swifts drivrutin definierar -emit-object som utdata i form av en objektfil och -parse-as-library som att källan parsas som ett bibliotek i stället för som ett skript. Då blir det C-filen som tillhandahåller programmets startpunkt. Swift compiler driver options

Trots sitt historiska namn begär -emit-objc-header-path även kompatibilitetsheaderns C-avsnitt för @c-deklarationer. Det implementerade förslaget behåller uttryckligen flaggan för detta ändamål. Ersätt inte den genererade headern manuellt med en antagen funktionsprototyp. C compatible functions and enums

Det andra kommandot är det avgörande testet av källspråket. Clang beskriver -x som valet av indataspråk och -c som att skapa en objektfil utan att länka. Här gör -x c -std=c11 att anroparen kompileras som C11 samtidigt som den faktiska genererade headern läses in. Clang command guide

Det sista kompilatoranropet länkar båda objektfilerna med Swift-drivrutinen. Swifts egen kompilatorguide rekommenderar att man ber swiftc länka filer i stället för att manuellt sätta samman ett länkningskommando på lägre nivå. Debugging the Compiler

Förväntade utdata, utifrån exemplens grenar, är:

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

Dessa kommandon har granskats mot den hämtade dokumentationen men kördes inte här: forskningsmiljön är Linux och har varken swiftc eller Clang installerat. Utdata ovan är ett förväntat resultat, inte ett rapporterat test på Mac.

Välj ett gränssnitt som passar din befintliga kod

För ett nytt, litet gränssnitt med Swift 6.3 eller senare kopplar metoden med den genererade headern deklarationen direkt till implementationen. Versionsdokumentationen stöder även en andra riktning: @c @implementation implementerar en funktion som redan deklarerats i en importerad C-header, och Swift kontrollerar att signaturerna stämmer överens. Det är användbart när ett befintligt C-API måste fortsätta vara gränssnittets kontrakt. Swift 6.3 Released

Om det omgivande projektet redan anropar Swift genom C++ är Swifts genererade C++-bindningar ett annat alternativ. Publika Swift-funktioner representeras av inline C++-funktioner inuti ett modulnamnrymd. En C-anropare behöver en separat adapter som kompileras som C++ och exponerar en liten C-deklaration. C-översättningsenheten inkluderar adapterns C-header. Mixing Swift and C++

I en sådan adapter anger extern \"C\" C-språklänkning för C++-deklarationen eller definitionen. Det omvandlar inte innehållet i en C++-header till C-syntax. C++-arbetsutkastet definierar länkningsspecifikationer och skiljer språklänkning från vanliga deklarationer. C++ working draft: Linkage specifications

För den här handledningen är det raka, stödda alternativet att uppgradera en äldre kompilator till en version som stöder @c. Förslaget beskriver @_cdecl som dess experimentella föregångare; om man kopierar den äldre stavningen till ett nytt exempel döljer man en betydelsefull versionsgräns. C compatible functions and enums

Felsök vid den gräns där felet uppstår

Använd det första byggsteget som misslyckas för att avgränsa undersökningen. Fel vid generering av headern, parserfel i C och odefinierade symboler kräver olika åtgärder.

Första felet Nästa kontroll
Swift avvisar @c Kontrollera den valda Swift-kompilatorns version igen; det här exemplet kräver Swift 6.3 eller senare.
C hittar inte Percent-Swift.h Bekräfta att Swift-kommandot lyckades och att båda filerna byggs från den avsedda katalogen.
C hittar inte demo_clamp_percent Granska den nyligen genererade headern och kontrollera attributets stavning; bygg om efter ändringar i källkoden.
C rapporterar C++- eller Objective-C-syntax Kontrollera vilken header som inkluderades och vilken verktygskedja som genererade den. Behåll den uttryckliga C-kompileringen medan du undersöker felet.
Länkningen kan inte hitta den exporterade funktionen Bekräfta att Percent.o ingår i det sista kommandot och byggdes om från den aktuella Swift-källkoden.
Programmet ger oväntade resultat Läs det utskrivna paret av indata och resultat och jämför med Swift-grenarna.

Den här sekvensen följer kompilatorstegen som Clang dokumenterar: parsning avgör om källkoden är giltig, generering av objektfiler skapar kompilerad kod och länkning kombinerar de kompilerade indatafilerna. En lyckad C-kompilering fastställer alltså bara en del av resultatet. Clang command guide

När du anpassar exemplet ska du hålla C-gränssnittets funktion liten och ge varje ny parameter ett uttryckligt kontrakt. För text bör du bestämma teckenkodning och längdhantering; för buffertar bör du bestämma kapacitet och ägarskap innan du skriver adaptern. Det är beslut om gränssnittsdesign som ligger utanför den här demonstrationen med enbart heltal.

Den färdiga lokala kontrollen ger tre observerbara resultat: den genererade headern accepteras vid uttrycklig C-kompilering, båda implementationerna länkas till en körbar fil och anroparen verifierar de valda indata-värdena. Behåll dessa kontroller när du ersätter den syntetiska beräkningen med din egen Swift-funktion.

Källor