Avec Swift 6.3 ou une version ultérieure, marquez une fonction globale avec @c, générez son en-tête de compatibilité, compilez l’appelant en C et liez le tout avec swiftc. Ce guide construit un petit programme natif en ligne de commande pour Mac qui illustre chacune de ces étapes.

La tâche visée est précise : appeler un calcul Swift depuis un fichier .c ordinaire, sans compiler ce fichier en Objective-C ou en C++. La documentation officielle de Swift identifie @c comme la fonctionnalité d’interopérabilité avec le C prise en charge, introduite dans Swift 6.3. Vérifiez la version de votre compilateur avant de suivre d’anciens conseils sur les attributs expérimentaux. Swift 6.3 Released

Qu’est-ce qui permet d’appeler une fonction Swift depuis le C ?

Trois éléments doivent concorder : la déclaration lue par le compilateur C, la convention d’appel utilisée par l’implémentation et les fichiers objets fournis à l’éditeur de liens. Un en-tête répond au premier point ; il ne peut ni fournir une implémentation manquante ni modifier l’ABI d’une fonction existante.

L’attribut @c donne à une fonction globale Swift la convention d’appel du C et limite sa signature aux types représentables en C. Un argument d’attribut facultatif permet de choisir son nom en C. Swift émet la déclaration correspondante dans la partie compatible avec le C de son en-tête de compatibilité généré. Ces comportements sont spécifiés dans la proposition Swift Evolution mise en œuvre. C compatible functions and enums

Dans cet exemple, l’interface accepte et renvoie un entier C. Le calcul fictif limite un pourcentage d’affichage à l’intervalle inclusif de zéro à 100. Il n’y a ni tampon, ni résultat alloué, ni durée de vie d’objet à coordonner. La première compilation peut ainsi se concentrer sur la compilation et l’édition de liens.

Vérifier d’abord la chaîne d’outils Mac

Il vous faut un environnement de développement macOS avec Clang, un SDK macOS et Swift 6.3 ou une version ultérieure. Apple fournit les outils en ligne de commande avec Xcode et propose également un paquet Command Line Tools distinct. Si vous avez déjà Xcode, Apple indique qu’il n’est pas nécessaire d’installer séparément ces outils. Installing the command-line tools

Exécutez ces vérifications dans Terminal :

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

Vérifiez la version du compilateur Swift lui-même. Un compilateur plus ancien ne prend pas en charge la fonctionnalité @c simplement parce qu’un SDK plus récent est installé. Si plusieurs versions de Xcode sont présentes, Apple explique comment sélectionner la version active dans Xcode > Settings > Locations > Command Line Tools, ou la remplacer pour une session shell avec DEVELOPER_DIR. Configuring command-line tools settings

Les commandes ci-dessous utilisent xcrun pour lancer les outils de développement sélectionnés. Apple décrit ce rôle de découverte des outils dans sa référence sur la compilation en ligne de commande. Building from the Command Line with Xcode FAQ

Créez un répertoire de travail vide et enregistrez-y les deux fichiers source ci-dessous. Exécutez les commandes de compilation depuis ce répertoire. Il s’agit d’une compilation native pour le Mac local ; elle ne crée pas de binaire universel et ne configure pas d’autre plateforme.

Écrire l’implémentation Swift et l’appelant C

Définir la fonction Swift exportée

Enregistrez le fichier source Swift complet suivant sous le nom Percent.swift :

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

Le nom Swift est clampPercent ; le nom exposé au C est demo_clamp_percent. CInt correspond au int du C. Cette correspondance explicite évite de supposer que Swift Int signifie C int : le type C documenté pour Swift Int est ptrdiff_t. C compatible functions and enums

La fonction compare des valeurs et renvoie soit une entrée, soit une constante. Son calcul ne provoque aucun dépassement arithmétique, y compris pour les entrées hors de la plage des pourcentages. Le préfixe du nom exporté est un exemple de convention permettant de distinguer cette interface des autres fonctions d’un programme plus vaste.

L’appeler depuis un programme C complet

Enregistrez ce fichier sous le nom main.c. La compilation créera l’en-tête inclus :

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

Les trois entrées testent la borne inférieure, une valeur inchangée et la borne supérieure. Le programme vérifie explicitement les résultats et renvoie un code différent de zéro en cas d’écart ; sa sortie est donc plus utile qu’une simple démonstration qui affiche un nombre.

Générer l’en-tête, compiler le C et lier le programme

Exécutez cette séquence dans un shell, depuis le répertoire contenant les deux fichiers. Chaque étape interrompt la séquence en cas d’échec :

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

La première commande produit l’objet Swift et l’en-tête de compatibilité. Le pilote Swift définit -emit-object comme la génération d’un fichier objet et -parse-as-library comme l’analyse d’un fichier source de bibliothèque plutôt que d’un script. Le point d’entrée de l’exécutable reste ainsi dans le fichier C. Swift compiler driver options

Malgré son nom historique, -emit-objc-header-path génère également la section C de l’en-tête de compatibilité pour les déclarations @c. La proposition mise en œuvre conserve explicitement ce drapeau à cette fin. Ne remplacez pas manuellement l’en-tête généré par un prototype supposé. C compatible functions and enums

La deuxième commande constitue le test déterminant du langage source. Clang indique que -x sert à sélectionner le langage d’entrée et que -c produit un fichier objet sans édition de liens. Ici, -x c -std=c11 permet de compiler l’appelant en C11 tout en lisant l’en-tête réellement généré. Clang command guide

La dernière invocation du compilateur lie les deux fichiers objets avec le pilote Swift. Le guide du compilateur Swift recommande de demander à swiftc de lier les fichiers plutôt que d’assembler manuellement une commande d’édition de liens de niveau inférieur. Debugging the Compiler

La sortie attendue, déduite des conditions de l’exemple, est :

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

Ces commandes ont été examinées à la lumière de la documentation consultée, mais n’ont pas été exécutées ici : l’environnement de recherche est Linux et ne dispose ni de swiftc ni de Clang. La sortie ci-dessus est le résultat attendu, et non le compte rendu d’un test sur Mac.

Choisir l’interface adaptée au code existant

Pour une interface nouvelle et réduite avec Swift 6.3 ou une version ultérieure, l’approche fondée sur l’en-tête généré ci-dessus lie directement la déclaration à l’implémentation. La documentation de la version prend également en charge un second sens : @c @implementation implémente une fonction déjà déclarée dans un en-tête C importé, et Swift vérifie la concordance des signatures. Cette solution convient lorsqu’une API C existante doit rester le contrat de l’interface. Swift 6.3 Released

Si le projet environnant appelle déjà Swift par l’intermédiaire du C++, les liaisons C++ générées par Swift offrent une autre possibilité. Les fonctions Swift publiques sont représentées par des fonctions C++ en ligne dans un espace de noms de module. Un appelant C aurait besoin d’un adaptateur distinct, compilé en C++, qui expose une petite déclaration accessible au C. L’unité de traduction C inclurait l’en-tête C de cet adaptateur. Mixing Swift and C++

Dans un tel adaptateur, extern \"C\" spécifie la liaison de langage C sur la déclaration ou la définition C++. Cela ne convertit pas le contenu d’un en-tête C++ en syntaxe C. Le projet de norme C++ définit les spécifications de liaison et distingue la liaison de langage des déclarations ordinaires. C++ working draft: Linkage specifications

Pour ce tutoriel, mettre à niveau un ancien compilateur vers une version qui prend en charge @c est la voie directe prévue. La proposition décrit @_cdecl comme son prédécesseur expérimental ; reprendre cette ancienne syntaxe dans un nouvel exemple masquerait une différence de version importante. C compatible functions and enums

Diagnostiquer les erreurs à l’étape où elles surviennent

Servez-vous de la première étape de compilation en échec pour circonscrire le problème. Un échec de génération d’en-tête, une erreur d’analyse C et un symbole introuvable appellent des corrections différentes.

Première erreur Vérification suivante
Swift rejette @c Vérifiez à nouveau la version du compilateur Swift sélectionné ; cet exemple requiert Swift 6.3 ou une version ultérieure.
Le C ne trouve pas Percent-Swift.h Vérifiez que la commande Swift a réussi et que les deux fichiers sont compilés depuis le répertoire prévu.
Le C ne trouve pas demo_clamp_percent Examinez l’en-tête qui vient d’être généré et vérifiez l’orthographe de l’attribut ; relancez la compilation après toute modification des sources.
Le C signale une syntaxe C++ ou Objective-C Vérifiez quel en-tête a été inclus et quelle chaîne d’outils l’a généré. Maintenez explicitement la compilation en C pendant l’analyse du problème.
L’édition de liens ne trouve pas la fonction exportée Vérifiez que Percent.o figure dans la commande finale et qu’il a été recompilé depuis la version actuelle du code Swift.
L’exécutable affiche des résultats inattendus Lisez la paire entrée/résultat affichée et comparez-la aux conditions Swift.

Cette séquence suit les étapes du compilateur documentées par Clang : l’analyse syntaxique établit si le code source est valide, la génération d’objets produit le code compilé, puis l’édition de liens combine les fichiers compilés. Une compilation C réussie ne valide donc qu’une partie de la tâche. Clang command guide

Lors de l’adaptation de l’exemple, gardez la fonction exposée au C réduite et définissez explicitement le contrat de chaque nouveau paramètre. Pour le texte, décidez de l’encodage et de la gestion de la longueur ; pour les tampons, déterminez la capacité et la propriété avant d’écrire le wrapper. Ces décisions de conception de l’interface dépassent le cadre de cette démonstration limitée aux entiers.

La vérification locale finale produit trois résultats observables : l’en-tête généré est accepté lors de la compilation explicitement effectuée en C, les deux implémentations sont liées dans un exécutable unique et l’appelant vérifie les entrées choisies. Conservez ces vérifications lorsque vous remplacerez le calcul fictif par votre propre fonction Swift.

Sources