Comment appeler C depuis Swift?

136

Existe-t-il un moyen d'appeler des routines C depuis Swift?

De nombreuses bibliothèques iOS / Apple sont uniquement en C et j'aimerais toujours pouvoir les appeler.

Par exemple, j'aimerais pouvoir appeler les bibliothèques d'exécution objc depuis swift.

En particulier, comment reliez-vous les en-têtes iOS C?

Flamber
la source

Réponses:

106

Oui, vous pouvez bien sûr interagir avec les bibliothèques Apples C. Voici comment.
Fondamentalement, les types C, les pointeurs C, etc. sont traduits en objets Swift, par exemple un C intdans Swift est un CInt.

J'ai construit un petit exemple, pour une autre question, qui peut être utilisée comme une petite explication, sur la façon de faire le pont entre C et Swift:

main.swift

import Foundation

var output: CInt = 0
getInput(&output)

println(output)

UserInput.c

#include <stdio.h>

void getInput(int *output) {
    scanf("%i", output);
}

cliinput-Bridging-Header.h

void getInput(int *output);

Voici la réponse originale.

Leandros
la source
1
Je suis nouveau sur ios / swift. Je voudrais utiliser la fonction C de journalisation de #include <asl.h> dans des fichiers swift. N'importe qui?
Dmitry Konovalov
est-il compatible avec les fichiers C ++, .cpp ??
Carlos.V
Pour utiliser directement les fichiers C ++, vous devez créer un wrapper objective-C. Vous voudrez que l'implémentation (qui devrait résider d'elle-même dans un fichier .mm) contienne du code Objective-C ++ (qui est juste Objective-C et C ++ dans un seul fichier), et l'interface (qui devrait être dans son propre .h header) doit contenir du code Objective-C pur, vous devrez donc convertir les types C ++ en types Objective-C dans l'implémentation, pour les exposer à Swift. Ensuite, vous pouvez importer cet en-tête avec un en-tête de pont objectif-c.
William T Froggard
2
"Vous pouvez bien sûr interagir avec les bibliothèques Apples C" Incorrect. Vous pouvez interagir avec toutes les bibliothèques C, pas du tout limitées à celles d'Apple.
Slipp D.Thompson
Mise à jour du lien de documentation developer.apple.com/documentation/swift/…
MechEthan
9

Le compilateur convertit l'API C en Swift comme il le fait pour Objective-C.

import Cocoa

let frame = CGRect(x: 10, y: 10, width: 100, height: 100)

import Darwin

for _ in 1..10 {
    println(rand() % 100)
}

Voir Interaction avec les API Objective-C dans la documentation.

Rickster
la source
1
Merci pour l'exemple, et vous avez raison, je peux voir comment cela pourrait fonctionner. Cependant, cela ne répond pas vraiment à ma question, à savoir comment appeler les bibliothèques Apple C. Mais c'est certainement le plus proche.
Blaze le
Regardez ailleurs dans ce document. Par exemple, les "objets" de style CoreFoundation ( CFTypeRef) sont convertis en objets Swift. Cependant, la plupart des fonctions ObjCRuntime.h ne sont pas significatives pour Swift.
rickster le
"La plupart des fonctions ObjCRuntime.h ne sont pas significatives pour Swift, cependant" Pourquoi dites-vous cela? Je pense que j'ai besoin de trouver un moyen d'importer un en-tête et de le ponter. Cela semble gênant, mais je suppose que c'est la voie à suivre.
Blaze le
5

Juste au cas où vous êtes aussi nouveau que moi sur XCode et que vous souhaitez essayer les extraits publiés dans la réponse de Leandro :

  1. Fichier-> Nouveau-> Projet
  2. choisissez Outil de ligne de commande comme préréglage de projet et nommez le projet "cliinput"
  3. faites un clic droit dans le navigateur de projet (le panneau bleu à gauche) et choisissez "Nouveau fichier ..."
  4. Dans la boîte de dialogue déroulante, nommez le fichier "UserInput". Décochez la case "Créer également un fichier d'en-tête". Une fois que vous avez cliqué sur "Suivant", il vous sera demandé si XCode doit créer le fichier Bridging-Header.h pour vous. Choisissez "Oui".
  5. Copiez et collez le code de la réponse de Leandro ci-dessus. Une fois que vous avez cliqué sur le bouton de lecture, il doit être compilé et exécuté dans le terminal, qui dans xcode est intégré dans le panneau inférieur. Si vous entrez un numéro dans le terminal, un numéro vous sera renvoyé.
lukas83
la source
4

Cet article a également une bonne explication sur la façon de le faire en utilisant le support du module de clang .

Il est encadré en termes de comment faire cela pour le projet CommonCrypto, mais en général, cela devrait fonctionner pour toute autre bibliothèque C que vous souhaitez utiliser à partir de Swift.

J'ai brièvement expérimenté cette opération pour zlib. J'ai créé un nouveau projet de cadre iOS et créé un répertoire zlib, contenant un fichier module.modulemap avec les éléments suivants:

module zlib [system] [extern_c] {
    header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/zlib.h"
    export *
}

Ensuite, sous Targets -> Link Binary With Libraries, j'ai sélectionné ajouter des éléments et ajouté libz.tbd.

Vous voudrez peut-être construire à ce stade.

J'ai alors pu écrire le code suivant:

import zlib

public class Zlib {
    public class func zlibCompileFlags() -> UInt {
        return zlib.zlibCompileFlags()
    }
}

Vous n'êtes pas obligé de mettre le nom de la bibliothèque zlib devant, sauf dans le cas ci-dessus, j'ai nommé la fonction de classe Swift de la même manière que la fonction C, et sans la qualification, la fonction Swift finit par être appelée à plusieurs reprises jusqu'à ce que l'application s'arrête.

julien
la source
3

Dans le cas de C ++, il y a cette erreur qui apparaît:

  "_getInput", referenced from: 

Vous avez également besoin d'un fichier d'en-tête C ++. Ajoutez c-linkage à votre fonction, puis incluez le fichier d'en-tête dans l'en-tête du pont:

Swift 3

UserInput.h

#ifndef USERINPUT_H
#define USERINPUT_H    

#ifdef __cplusplus
extern "C"{
#endif

getInput(int *output);

#ifdef __cplusplus
}
#endif

UserInput.c

#include <stdio.h>

void getInput(int *output) {
    scanf("%i", output);
}    

main.swift

import Foundation
var output: CInt = 0
getInput(&output)
print(output)

cliinput-Bridging-Header.h

#include "UserInput.h"

Voici la vidéo originale expliquant cela

John James
la source
si cela ne fonctionne pas, essayez d'ajouter un __OBJCchèque à votre en-tête de pont, par exemple#ifdef __OBJC @import UIKit; #endif
Chris Yim
1

Il semble que ce soit une boule de cire assez différente lorsqu'il s'agit de pointeurs. Voici ce que j'ai jusqu'à présent pour appeler l' readappel système C POSIX :

enum FileReadableStreamError : Error {
case failedOnRead
}

// Some help from: http://stackoverflow.com/questions/38983277/how-to-get-bytes-out-of-an-unsafemutablerawpointer
// and https://gist.github.com/kirsteins/6d6e96380db677169831
override func readBytes(size:UInt32) throws -> [UInt8]? {
    guard let unsafeMutableRawPointer = malloc(Int(size)) else {
        return nil
    }

    let numberBytesRead = read(fd, unsafeMutableRawPointer, Int(size))

    if numberBytesRead < 0 {
        free(unsafeMutableRawPointer)
        throw FileReadableStreamError.failedOnRead
    }

    if numberBytesRead == 0 {
        free(unsafeMutableRawPointer)
        return nil
    }

    let unsafeBufferPointer = UnsafeBufferPointer(start: unsafeMutableRawPointer.assumingMemoryBound(to: UInt8.self), count: numberBytesRead)

    let results = Array<UInt8>(unsafeBufferPointer)
    free(unsafeMutableRawPointer)

    return results
}
Chris Prince
la source