2014-12-13 11 views
18

Ich habe ein Swift-Programm, das Interop mit einer C-Bibliothek. Diese C-Bibliothek gibt eine Struktur mit einem char[] Array nach innen, wie folgt aus:Ein C-Char-Array in einen String konvertieren

struct record 
{ 
    char name[8]; 
}; 

Die Definition wird korrekt in Swift importiert. Das Feld wird jedoch als Tupel von 8 Int8 Elemente (typisiert (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)) interpretiert, die ich keine Ahnung habe, wie mit Swift in String umgewandelt werden.

Es gibt keinen String Initialisierer, der ein Int8 Tupel akzeptiert, und es scheint nicht möglich, einen Zeiger auf das erste Element des Tupels zu bekommen (da Typen heterogen sein können, ist das nicht wirklich überraschend).

Jetzt ist meine beste Idee, eine kleine C-Funktion zu erstellen, die einen Zeiger auf die Struktur selbst akzeptiert und name als char* Zeiger anstelle eines Arrays zurückgibt, und damit gehen.

Gibt es aber pure schnelle Wege es zu tun?

+2

Sind Sie sicher, Interop macht es zu einer C-Frage? Oder dass Ihr Workaround dies tut? Vor allem, weil Sie eine pure-schnelle Lösung wollen ... – Deduplicator

+0

@Deduplicator, wenn ich nach einem C char-Array in eine Swift-Zeichenfolge konvertieren würde, würde ich nach Tags "c" und "swift" sicher suchen. – zneak

+1

Es gibt nichts C über dieses Byte-Array, aber Sie haben eine Beschreibung davon in C, C++, Ziel-C, Ziel-C++ und so weiter. Es ist keine C-Frage. – Deduplicator

Antwort

21

Die C-Array char name[8] als Tupel zu Swift importiert wird:

Die Adresse name ist die gleiche wie die hinzufügen resse von name[0] und Swift Konserven das Speicherlayout von Strukturen aus C importiert, wie confirmed by Apple engineer Joe Groff:

... Sie können die Struktur in C und importieren Sie es in Swift definiert verlassen. Swift respektiert das Layout von C.

Als Folge haben wir die Adresse record.name passieren können, umgewandelt zu einem UInt8 Zeiger auf den String initializer:

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

HINWEIS: Es wird angenommen, dass der Bytes in name[] sind eine gültige NUL-terminierte UTF-8-Sequenz.

+1

Ja, das funktioniert. Ich füge nur hinzu, dass 'record' ** needs ** veränderbar sein muss (deklariert mit' var') oder der Swift-Compiler einen seltsamen Fehler erzeugt. – zneak

+0

@zneak: Sie haben Recht, ich habe diese Informationen zu der Antwort hinzugefügt. –

+0

Ich glaube, das wird funktionieren, aber ist dokumentiert, dass Tupel zusammenhängend im Speicher sind? Bei Arrays erhalten Sie diese Gewissheit erst, wenn Sie 'withUnsafeBufferPointer' aufrufen, aber die Tupel-Implementierung wurde nicht dokumentiert, soweit ich das beurteilen kann. –

1

Try this:

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 
} 
+0

Die C-Zeichenfolge ist kein 'UnsafePointer ', es ist ein Tupel von 8 'Int8'-Elementen, so dass diese Methode mein Problem nicht löst. Außerdem hat die 'String' Klasse eine 'init?(UTF8String: UnsafePointer ) 'Failable Initialisierer, der genau das tut. – zneak

+0

In meiner Anwendung muss ich die aktuelle OpenGL-Version protokollieren, um sicherzustellen, dass das Pixelformat korrekt eingestellt wurde. Ich mache dies mit dem Befehl glVersionCString: UnsafePointer = glGetString (GLenum (GL_VERSION)). Der Compiler lässt mich die Rückgabe von glGetString nicht als unsafePointer , so dass ich den Swift String Initialisierer nicht verwenden kann. Das ist der Grund für diese Funktion. – jwlaughton

+0

Ich stelle nicht die Nützlichkeit Ihrer Funktion in Ihrem spezifischen Anwendungsfall in Frage, aber es trifft hier nicht zu. Dies funktioniert für Sie, weil 'glGetString' einen Zeiger zurückgibt. Die "struct", mit der ich mich beschäftige, hat ein Array-Feld, das sich sehr von einem Pointer-Feld unterscheidet. Wie gesagt, der Swift-Typ des Feldes ist "(Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)", nicht "UnsafePointer ". – zneak

3

Sie können tatsächlich ein Tupel in ein Array sammeln von Swifts variadische Parameter Syntax:

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") 
+0

Das sieht gut aus (wusste nicht über die variable Tupelsyntax), obwohl im * realen Fall * das Tupel 32 Elemente hat, und ich könnte sehen, dass ich das für größere Arrays (wie 128 Elemente) brauche. Das würde eine ärgerlich große Art Annotation machen. Siehst du einen Weg, um es unabhängig von der Anzahl der Elemente zu machen? – zneak

+0

Welches Konstrukt ist 'let (int8s: Int8 ...)'? Ist "let (name: type) = exp" im Allgemeinen dasselbe wie "let name: type = expr"? –

+0

@zneak In diesem Fall müssen Sie es inline ausführen, da Sie bei einer Funktion die richtige Anzahl von Elementen eingeben müssen, um sie an die jeweilige Tupelarität anzupassen. Ich habe meine Antwort geändert, um zu zeigen, was Sie tun können. –

2

Ich interessiere mich für diese in der Arbeit als auch für meine eigenen Zwecke aus, so habe ich eine neue Funktion:

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 
} 

Ich nenne diese Funktion mit:

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

die resultierende Ausgabe ist :

testSwiftString = test

1

Hier ist eine Lösung kam ich mit, was verwendet Reflection, um das Tupel tatsächlich in ein [Int8] zu konvertieren (siehe Any way to iterate a tuple in swift?), und konvertiert es dann mithilfe von fromCString ...() -Methoden in einen String.

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 
    } 
} 
0

Swift 3. Verwendet nur Reflexion. Diese Version beendet die Erstellung der Zeichenfolge, wenn sie auf ein Nullbyte trifft. Geprüft.

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 
} 
0

Ich habe gerade ein ähnliches Problem mit Swift 3. (3.0.2) erlebt. Ich habe versucht, ein Array von CChar [CChar] zu einem String in Swift zu konvertieren. Es stellt sich heraus, dass Swift 3 einen String-Initialisierer hat, der einen cString braucht.

Beispiel:

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

Ergebnisse in

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

b = Optional ("abc")

Hinweis dass die cString-Funktion für String zu einem Optional führt. Es muss erzwungen werden, wenn es in der Funktion String.init verwendet wird, die b erstellt. Und b ist auch optional ... was bedeutet, dass beide am Ende Null sein könnten, daher sollte auch eine Fehlerprüfung verwendet werden.

Verwandte Themen