arrays - pass - unsafepointer cchar




Converting a C char array to a String (5)

Here's a solution I came up with which uses reflection to actually convert the tuple into an [Int8] (see Any way to iterate a tuple in swift? ), and then converts it to a string using fromCString...() methods.

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

I have a Swift program that does interop with a C library. This C library returns a structure with a char[] array inside, like this:

struct record
{
    char name[8];
};

The definition is correctly imported into Swift. However, the field is interpreted as a tuple of 8 Int8 elements (typed (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8) ), which I have no idea how to transform into a String with Swift.

There is no String initializer that accepts an Int8 tuple, and it doesn't seem possible to get a pointer to the first element of the tuple (since types can be heterogenous, that's not really surprising).

Right now, my best idea is to create a tiny C function that accepts a pointer to the structure itself and return name as a char* pointer instead of an array, and go with that.

Is there, however, are pure Swift way to do it?


I have just experienced a similar issue using Swift 3. (3.0.2). I was attempting to convert an Array of CChar, [CChar] to a String in Swift. It turns out Swift 3 has a String initializer which will take a cString.

Example:

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

results in

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

b = Optional("abc")

Note that the cString function on String results in an Optional. It must be force unwrapped when used in the String.init function creating b. And b is also Optional... meaning both could end up being nil, so error checking should also be used.


Swift 3. Only uses reflection. This version stops building the string when it encounters a null byte. Tested.

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
}

The C array char name[8] is imported to Swift as a tuple:

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

The address of name is the same as the address of name[0] , and Swift preserves the memory layout of structures imported from C, as confirmed by Apple engineer Joe Groff:

... You can leave the struct defined in C and import it into Swift. Swift will respect C's layout.

As a consequence, we can pass the address of record.name , converted to an UInt8 pointer, to the 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)
    }
}

NOTE: It is assumed that the bytes in name[] are a valid NUL-terminated UTF-8 sequence.


You can actually collect a tuple into an array by using Swift's variadic 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")




tuples