web-dev-qa-db-fra.com

Conversion d'un tableau de caractères C en chaîne

J'ai un programme Swift qui interopère avec une bibliothèque C. Cette bibliothèque C retourne une structure avec un tableau char[] à l'intérieur, comme ceci:

struct record
{
    char name[8];
};

La définition est correctement importée dans Swift. Cependant, le champ est interprété comme un Tuple de 8 éléments Int8 (saisis (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)), que je ne sais pas comment transformer en un String avec Swift.

Il n'y a pas d'initialisateur String qui accepte un tuple Int8 et il ne semble pas possible de faire un pointeur sur le premier élément du tuple (les types pouvant être hétérogènes, ce n'est pas vraiment surprenant).

Pour le moment, ma meilleure idée est de créer une petite fonction C qui accepte un pointeur sur la structure elle-même et renvoie name sous la forme d'un pointeur char* au lieu d'un tableau, et le reste.

Y at-il, cependant, pur Swift le moyen de le faire?

20
zneak

Le tableau C char name[8] est importé dans Swift en tant que tuple:

(Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)

L'adresse de name est la même que celle de name[0] et Swift conserve la disposition de la mémoire des structures importées de C, comme confirmée par l'ingénieur Apple Joe Groff:

... Vous pouvez laisser la structure définie en C et l'importer dans Swift. Swift respectera la mise en page de C. 

En conséquence, nous pouvons passer l'adresse de record.name, Convertie en un pointeur UInt8, en L'initialiseur de chaîne:

var record = someFunctionReturningAStructRecord()

// Swift 2:
let name = withUnsafePointer(&record.name) {
    String.fromCString(UnsafePointer($0))!
}

// Swift 3:
let name = withUnsafePointer(to: &record.name) {
    $0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: record.name)) {
        String(cString: $0)
    }
}

NOTE: Il est supposé que les octets dans name[] sont une séquence UTF-8 valide terminée par NUL.

26
Martin R

Vous pouvez réellement collecter un tuple dans un tableau en utilisant la syntaxe de paramètre variadique de Swift:

let record = getRecord()
let (int8s: Int8...) = myRecord          // int8s is an [Int8]
let uint8s = int8s.map { UInt8($0) }
let string = String(bytes: uint8s, encoding: NSASCIIStringEncoding)
// myString == Optional("12345678")
3
Nate Cook

Je suis intéressé à travailler sur cela pour mes propres fins aussi, alors j'ai ajouté une nouvelle fonction:

func asciiCArrayToSwiftString(cString:Int8...) -> String
{
    var swiftString = String()            // The Swift String to be Returned is Intialized to an Empty String
    var workingCharacter:UnicodeScalar = UnicodeScalar(UInt8(cString[0]))
    var count:Int = cString.count

    for var i:Int = 0; i < count; i++
    {
        workingCharacter = UnicodeScalar(UInt8(cString[i])) // Convert the Int8 Character to a Unicode Scalar
        swiftString.append(workingCharacter)             // Append the Unicode Scalar

    }

    return swiftString                     // Return the Swift String
}

J'appelle cette fonction avec:

    let t:Int8 = Int8(116)
    let e:Int8 = Int8(101)
    let s:Int8 = Int8(115)
    let testCString = (t, e, s, t)
    let testSwiftString = wispStringConverter.asciiCArrayToSwiftString(testCString.0, testCString.1, testCString.2, testCString.3)
    println("testSwiftString = \(testSwiftString)")

le résultat obtenu est:

testSwiftString = test

2
jwlaughton

Swift 3. N'utilise que la réflexion. Cette version cesse de construire la chaîne lorsqu'elle rencontre un octet nul. Testé.

func TupleOfInt8sToString( _ tupleOfInt8s:Any ) -> String? {
    var result:String? = nil
    let mirror = Mirror(reflecting: tupleOfInt8s)

    for child in mirror.children {
        guard let characterValue = child.value as? Int8, characterValue != 0 else {
            break
        }

        if result == nil {
            result = String()
        }
        result?.append(Character(UnicodeScalar(UInt8(characterValue))))
    }

    return result
}
2
rosstulloch

Voici une solution que je propose qui utilise la réflexion pour convertir le Tuple en un [Int8] (voir Tout moyen pour itérer un Tuple dans swift? ), puis le convertit en chaîne en utilisant fromCString ... ) méthodes.

func arrayForTuple<T,E>(Tuple:T) -> [E] {
    let reflection = reflect(Tuple)
    var arr : [E] = []
    for i in 0..<reflection.count {
        if let value = reflection[i].1.value as? E {
            arr.append(value)
        }
    }
    return arr
}

public extension String {
    public static func fromTuple<T>(Tuple:T) -> String? {
        var charArray = arrayForTuple(Tuple) as [Int8]
        var nameString = String.fromCString(UnsafePointer<CChar>(charArray))
        if nameString == nil {
            nameString = String.fromCStringRepairingIllFormedUTF8(UnsafePointer<CChar>(charArray)).0
        }
        return nameString
    }
}
1
joeybladb

Essaye ça:

func asciiCStringToSwiftString(cString:UnsafePointer<UInt8>, maxLength:Int) -> String
{
    var swiftString = String()  // The Swift String to be Returned is Intialized to an Empty String
    var workingCharacter:UnicodeScalar = UnicodeScalar(cString[0])
    var count:Int = 0           // An Index Into the C String Array Starting With the First Character

    while cString[count] != 0             // While We Haven't reached the End of the String
    {
        workingCharacter = UnicodeScalar(cString[count]) // Convert the ASCII Character to a Unicode Scalar
        swiftString.append(workingCharacter)             // Append the Unicode Scalar Version of the ASCII Character
        count++                                          // Increment the Index to Look at the Next ASCII Character

        if count > maxLength                            // Set a Limit In Case the C string was Not NULL Terminated
        {
            if printDebugLogs == true
            {
                swiftString="Reached String Length Limit in Converting ASCII C String To Swift String"
            }
            return swiftString
        }
    }

    return swiftString                     // Return the Swift String
}
1
jwlaughton

Je viens de rencontrer un problème similaire avec Swift 3. (3.0.2). J'essayais de convertir un tableau de CChar, [CChar] en une chaîne dans Swift. Il s'avère que Swift 3 a un initialiseur de chaîne qui prendra un cString.

Exemple:

let a = "abc".cString(using: .utf8) // type of a is [CChar]
let b = String(cString: a!, encoding: .utf8) // type of b is String
print("a = \(a)")
print("b = \(b)")

résulte en

a = facultatif ([97, 98, 99, 0])

b = facultatif ("abc")

Notez que la fonction cString sur String a pour résultat facultatif. Il doit être forcé sans emballage lorsqu'il est utilisé dans la fonction String.init pour créer b. Et b est également facultatif ... ce qui signifie que les deux peuvent finir par être nuls. La vérification des erreurs doit donc également être utilisée.

1
Steve Rogers