Bridging [String] to const char * const *

Hi All.

When interfacing with C, Swift does some magic to auto-convert Swift strings to char *. This is great, but passing an array of string pointers gets much more involved. The type translates to UnsafePointer<UnsafePointer<CChar>> in Swift.

So I originally tried to get pointers to the individual strings by using cString(using:), and putting them into an Array, but then I found out that there is nothing holding on to the cStrings, so they go away before they can be used. I finally wound up with this hack:

public extension Array {
    public func cStringArray() throws -> ArrayBridge<Element,CChar> {
        return try ArrayBridge<Element,CChar>(array:self) {
            guard let item = $0 as? String,
                  let translated = item.cString(using: .utf8) else {
                throw hexdreamsCocoa.Errors.InvalidArgumentError
            }
            return translated
        }
    }
}

/*
We need to have this intermediate object around to hold on to the translated objects, otherwise they will go away.
The UnsafePointer won't hold on to the objects that it's pointing to.
*/
public struct ArrayBridge<SwiftType,CType> {

    let originals :[SwiftType]
    let translated :[[CType]]
    let pointers :[UnsafePointer<CType>?]
    public let pointer :UnsafePointer<UnsafePointer<CType>?>

    init(array :[SwiftType], transform: (SwiftType) throws -> [CType]) throws {
        self.originals = array
        self.translated = try array.map(transform)

        var pointers = [UnsafePointer<CType>?]()
        for item in translated {
            pointers.append(UnsafePointer<CType>(item))
        }
        pointers.append(nil)
        self.pointers = pointers
        self.pointer = UnsafePointer(self.pointers)
    }
}

And then to use it you would do something like

try stringArray.cStringArray().pointer

This all seems pretty ugly. So my question is: Is this the right way to handle this problem? Is there a simpler way? It would be awesome if Swift auto-converted arrays of Strings to const char * const *, since it’s a construct used so much in C.

Thanks!

-Kenny

Here's how we do it for internal testing purposes: withArrayOfCStrings <https://github.com/apple/swift/blob/dfc3933a05264c0c19f7cd43ea0dca351f53ed48/stdlib/private/SwiftPrivate/SwiftPrivate.swift#L68&gt;\. The callback is great because you don't have to worry about lifetimes.

Jordan

···

On Aug 18, 2016, at 13:04, Kenny Leung via swift-users <swift-users@swift.org> wrote:

Hi All.

When interfacing with C, Swift does some magic to auto-convert Swift strings to char *. This is great, but passing an array of string pointers gets much more involved. The type translates to UnsafePointer<UnsafePointer<CChar>> in Swift.

So I originally tried to get pointers to the individual strings by using cString(using:), and putting them into an Array, but then I found out that there is nothing holding on to the cStrings, so they go away before they can be used. I finally wound up with this hack:

public extension Array {
   public func cStringArray() throws -> ArrayBridge<Element,CChar> {
       return try ArrayBridge<Element,CChar>(array:self) {
           guard let item = $0 as? String,
                 let translated = item.cString(using: .utf8) else {
               throw hexdreamsCocoa.Errors.InvalidArgumentError
           }
           return translated
       }
   }
}

/*
We need to have this intermediate object around to hold on to the translated objects, otherwise they will go away.
The UnsafePointer won't hold on to the objects that it's pointing to.
*/
public struct ArrayBridge<SwiftType,CType> {

   let originals :[SwiftType]
   let translated :[[CType]]
   let pointers :[UnsafePointer<CType>?]
   public let pointer :UnsafePointer<UnsafePointer<CType>?>

   init(array :[SwiftType], transform: (SwiftType) throws -> [CType]) throws {
       self.originals = array
       self.translated = try array.map(transform)

       var pointers = [UnsafePointer<CType>?]()
       for item in translated {
           pointers.append(UnsafePointer<CType>(item))
       }
       pointers.append(nil)
       self.pointers = pointers
       self.pointer = UnsafePointer(self.pointers)
   }
}

And then to use it you would do something like

try stringArray.cStringArray().pointer

This all seems pretty ugly. So my question is: Is this the right way to handle this problem? Is there a simpler way? It would be awesome if Swift auto-converted arrays of Strings to const char * const *, since it’s a construct used so much in C.

Thanks!

-Kenny

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Thanks! At least it’s good to know there wasn’t something trivial I was missing.

-Kenny

···

On Aug 18, 2016, at 2:33 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Here's how we do it for internal testing purposes: withArrayOfCStrings. The callback is great because you don't have to worry about lifetimes.

Jordan

On Aug 18, 2016, at 13:04, Kenny Leung via swift-users <swift-users@swift.org> wrote:

Hi All.

When interfacing with C, Swift does some magic to auto-convert Swift strings to char *. This is great, but passing an array of string pointers gets much more involved. The type translates to UnsafePointer<UnsafePointer<CChar>> in Swift.

So I originally tried to get pointers to the individual strings by using cString(using:), and putting them into an Array, but then I found out that there is nothing holding on to the cStrings, so they go away before they can be used. I finally wound up with this hack:

public extension Array {
   public func cStringArray() throws -> ArrayBridge<Element,CChar> {
       return try ArrayBridge<Element,CChar>(array:self) {
           guard let item = $0 as? String,
                 let translated = item.cString(using: .utf8) else {
               throw hexdreamsCocoa.Errors.InvalidArgumentError
           }
           return translated
       }
   }
}

/*
We need to have this intermediate object around to hold on to the translated objects, otherwise they will go away.
The UnsafePointer won't hold on to the objects that it's pointing to.
*/
public struct ArrayBridge<SwiftType,CType> {

   let originals :[SwiftType]
   let translated :[[CType]]
   let pointers :[UnsafePointer<CType>?]
   public let pointer :UnsafePointer<UnsafePointer<CType>?>

   init(array :[SwiftType], transform: (SwiftType) throws -> [CType]) throws {
       self.originals = array
       self.translated = try array.map(transform)

       var pointers = [UnsafePointer<CType>?]()
       for item in translated {
           pointers.append(UnsafePointer<CType>(item))
       }
       pointers.append(nil)
       self.pointers = pointers
       self.pointer = UnsafePointer(self.pointers)
   }
}

And then to use it you would do something like

try stringArray.cStringArray().pointer

This all seems pretty ugly. So my question is: Is this the right way to handle this problem? Is there a simpler way? It would be awesome if Swift auto-converted arrays of Strings to const char * const *, since it’s a construct used so much in C.

Thanks!

-Kenny

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

withArrayOfCStrings looks great, however it returns [UnsafeMutablePointer<CChar>?] while C expects UnsafePointer<UnsafePointer<Int8>?>?

How may I convert it?

ps: changing as follow seems working:

public func withArrayOfCStrings<R>(
        _ args: [String],
        _ body: ([UnsafePointer<CChar>?]) -> R) -> R {

    let argsCounts = Array(args.map {
        $0.utf8.count + 1
    })
    let argsOffsets = [0] + scan(argsCounts, 0, +)
    let argsBufferSize = argsOffsets.last!

    var argsBuffer: [UInt8] = []
    argsBuffer.reserveCapacity(argsBufferSize)
    for arg in args {
        argsBuffer.append(contentsOf: arg.utf8)
        argsBuffer.append(0)
    }

    return argsBuffer.withUnsafeBufferPointer {
        (argsBuffer) in
        let ptr = UnsafeRawPointer(argsBuffer.baseAddress!)
                .bindMemory(to: CChar.self, capacity: argsBuffer.count)
        var cStrings: [UnsafePointer<CChar>?] = argsOffsets.map {
            ptr + $0
        }
        cStrings[cStrings.count - 1] = nil
        return body(cStrings)
    }
}
1 Like