4

I am using an existing C library in a Swift app and trying to convert a C character buffer to a Swift String.

Bridging.h

typedef struct { char mfg[8]; char model[8]; } motorcycle;

void GetMotorcycle(motorcycle *m);

Example.swift

var cycle = motorcycle(mfg: (0,0,0,0,0,0,0,0), model: (0,0,0,0,0,0,0,0));
GetMotorcycle(&cycle)
var manufacturer : String = String.fromCString(cycle.mfg)     // Error

This produces "Could not find a overload for 'fromCString' that accepts the supplied arguments"

Since Swift treats the C character array as a tuple, I cannot find a way to convert it to a Swift String.

7 Answers 7

3

Well, at least that C character array is only eight characters long, since there's currently no way to iterate over a tuple. Here's how you can convert it:

func char8ToString(tuple: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar)) -> String {
    let arr: unichar[] = [unichar(tuple.0), unichar(tuple.1),
                        unichar(tuple.2), unichar(tuple.3),
                        unichar(tuple.4), unichar(tuple.5),
                        unichar(tuple.6), unichar(tuple.7)]
    let len = arr.reduce(0) { $1 != 0 ? $0 + 1 : $0 }
    return NSString(characters: arr, length: len)
}

var manufacturer: String = char8ToString(cycle.mfg)

Hopefully we get language support for a better way to handle this case soon!

Sign up to request clarification or add additional context in comments.

3 Comments

The String produced with char8ToString() has trailing 0 characters. Assuming mfg=BMW and model=K1200S, this NSLog statement: NSLog("mfg = (char8ToString(cycle.mfg)), model = (char8ToString(cycle.model))") Produces: "mfg = BMW"
Thanks -- updated my answer to remove the trailing null characters.
Apparently, you can iterate over tuples as described here: stackoverflow.com/questions/24299045/…
3

Swift 5.2 — this solution can accepts arbitrary-length tuples, doesn’t do any extra copying, and uses the latest conventions for safe memory binding.

extension String {
    init<T>(tupleOfCChars: T, length: Int = Int.max) {
        self = withUnsafePointer(to: tupleOfCChars) {
            let lengthOfTuple = MemoryLayout<T>.size / MemoryLayout<CChar>.size
            return $0.withMemoryRebound(to: UInt8.self, capacity: lengthOfTuple) {
                String(bytes: UnsafeBufferPointer(start: $0, count: Swift.min(length, lengthOfTuple)), encoding: .utf8)!
            }
        }
    }
}

Comments

1

I have been in the same boat the last few days. I eventually came out with a general scheme to deal with C Arrays in C structs from C API, good if one has to deal with large arrays, but it must make use of "unsafeBitCast()". Also it must be adapted for each single fixed C array. This is the adaption to answer the current question (the code has been tested and it is working):

// This is our C array in this case char[8]
typealias CArray8Chars = (
    CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar
)
let zero8Chars: CArray8Chars = (
    0, 0, 0, 0, 0, 0, 0, 0
)
struct CString8  {
    private var value: CArray8Chars = zero8Chars
    var asString: String {
        mutating get {
            var cString = [CChar]()
            for i in 0..<sizeofValue(value) {
                cString += [self[i]]
                if self[i] == 0 {
                    break
                }
            }
            return String.fromCStringRepairingIllFormedUTF8(&cString).0!
        }
        set {
            let cchars = Array(newValue.utf8).map { CChar(Int8(bitPattern: UInt8($0))) }
            for i in 0..<min(cchars.count,sizeof(CArray8Chars))-1 { self[i] = cchars[i] }
            self[min(cchars.count,sizeof(CArray8Chars))-1] = 0
        }
    }
    subscript(i: Int) -> CChar {
        mutating get {
            let bytePtr = withUnsafePointer(&value, { (ptr) -> UnsafePointer<CChar> in
                return unsafeBitCast(ptr, UnsafePointer<CChar>.self)
            })
            return bytePtr[i]
        }
        set {
            let bytePtr = withUnsafeMutablePointer(&value, { (ptr) -> UnsafeMutablePointer<CChar> in
                return unsafeBitCast(ptr, UnsafeMutablePointer<CChar>.self)
            })
            bytePtr[i] = newValue
        }
    }
}

Now in the case of the motorcycle C struct it will be as following:

struct MotorCycle {
    var mfg     = CString8()
    var model   = CString8()
}
var cycle = MotorCycle()
GetMotorcycle(&cycle)
var manufacturer = cycle.mfg.asString // Since the string comes from C it works fine 

This also allows to set mfg/model using Unicode strings, but with a caveat:

// "💖" utf8 encoding makes the C string too big, so it gets cut. What's left represents an unknown char
cycle.model.asString = "I do 💖 Motor Bikes"
println(cycle.model.asString)

and makes the tuples indexable:

println(cycle.mfg[2])
cycle.model[2] = 32 // space. They are CChars, thus Int8

If one likes CString8 can be further extended, allowing to get/set substrings and the likes. But that's beyond the scope of this question.

Comments

1

Swift's built-in reflect() and MirrorType are pretty neat and allow us to inspect the tuple, determine its length, and iterate over its contents.

let tuple = (100, 120, 49, 100)
let mirror = reflect(tuple)

var string = String()
for index in 0..<mirror.count {
    let value = mirror[index].1.value as! Int
    let character = UnicodeScalar(value)
    string.append(character)
}

println(string) // prints "dx1d"

Comments

1

Here is a type safe solution that works on swift 2.1.

func tuple2string<T>(tuple: T) -> String {
    var result = Array<CChar>()
    let mirror = Mirror(reflecting: tuple)
    for child in mirror.children {
        if let value = child.value as? CChar {
            result.append(value)
        }
    }
    result.append(CChar(0))  // Null terminate

    return String.fromCString(result) ?? ""
}

However, this is pretty slow for bigger arrays because it needs to check every character for type, because the compiler has no way of knowing that the passed in tuple is all of the same type.

Comments

1

swift 3:

String(cString: UnsafeRawPointer([myTuple]).assumingMemoryBound(to: CChar.self))

Comments

1

Here is a solution supported by Swift 3 and Swift 4.

func stringFromCString(tupleOfBytes: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar)) -> String {
    var bytes = tupleOfBytes
    let string = withUnsafePointer(to: &bytes) { ptr -> String in
        return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self))
    }
    return string
}

Or if you want to use within line as a function with a tuple of bytes try this.

let string = withUnsafePointer(to: &tupleOfBytes) { ptr -> String in
    return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self))
}
return string

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.