Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting a C char array to a String

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?

like image 903
zneak Avatar asked Dec 13 '14 05:12

zneak


People also ask

Can you convert a char array to string?

Use the valueOf() method in Java to copy char array to string. You can also use the copyValueOf() method, which represents the character sequence in the array specified. Here, you can specify the part of array to be copied.

How do you convert char to string?

We can convert a char to a string object in java by using the Character. toString() method.

Is a char array a string in C?

This last part of the definition is important: all C-strings are char arrays, but not all char arrays are c-strings. C-strings of this form are called “string literals“: const char * str = "This is a string literal.


Video Answer


4 Answers

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)
    }
}
like image 122
Martin R Avatar answered Oct 22 '22 00:10

Martin R


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")
like image 4
Nate Cook Avatar answered Oct 22 '22 02:10

Nate Cook


I'm interested in working this out for my own purposes as well, so I added a new function:

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
}

I call this function with:

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

the resulting output is:

testSwiftString = test

like image 3
jwlaughton Avatar answered Oct 22 '22 01:10

jwlaughton


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.

like image 2
Steve Rogers Avatar answered Oct 22 '22 01:10

Steve Rogers