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?
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.
We can convert a char to a string object in java by using the Character. toString() method.
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.
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)
}
}
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")
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With