Converting a C char array to a String

zneak picture zneak · Dec 13, 2014 · Viewed 14.3k times · Source

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?

Answer

Martin R picture Martin R · Dec 13, 2014

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. The following code has been updated for Swift 4.2 and later:

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

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

For older versions of Swift:

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

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