0

Some C functions expect a char ** argument, like int main(int argc, char **argv). While Swift can automatically handle Char *, it doesn't handle char **. What's the best way to call such a function?

Revanth Kausikan
  • 673
  • 1
  • 9
  • 21
RopeySim
  • 423
  • 4
  • 11

1 Answers1

0

There are several "withXXX" methods around, but these require closures and get messy when there are multiple arrays. Still others use a separate "dealloc" function you must call when done. This goes against the Swift way. However, using a class just to get a deinit opportunity is wrong.

This class provides a pointer that works with char** and automatically deallocates the memory, even though it's a struct (using a little trick with a mapped data with deallocator).

public struct CStringArray {
    public let pointer: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>
    public let count: Int
    private var data: Data

    public init(_ array: [String]) {
        let count = array.count

        // Allocate memory to hold the CStrings and a terminating nil
        let pointer = UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>.allocate(capacity: count + 1)
        pointer.initialize(repeating: nil, count: count + 1)  // Implicit terminating nil at the end of the array

        // Populate the allocated memory with pointers to CStrings
        for (e, s) in array.enumerated() {
            pointer[e] = strdup(s)
        }

        // This uses the deallocator available on the data structure as a solution to the fact that structs do not have `deinit`
        self.data = Data(bytesNoCopy: pointer, count: MemoryLayout<UnsafeMutablePointer<CChar>>.size * count, deallocator: .custom({_,_ in
            for i in 0...count - 1 {
                free(pointer[i])
            }
            pointer.deallocate()
        }))

        self.pointer = pointer
        self.count = array.count
    }

    public subscript(index: Data.Index) -> UnsafeMutablePointer<CChar>? {
        get {
            precondition(index >= 0 && index < count, "Index out of range")
            return pointer[index]
        }
    }

    public subscript(index: Data.Index) -> String? {
        get {
            precondition(index >= 0 && index < count, "Index out of range")
            if let pointee = pointer[index] {
                return String(cString: pointee)
            }

            return nil
        }
    }
}
RopeySim
  • 423
  • 4
  • 11
  • "goes against Swift" is very subjective. And it is really contrasting with the fact that you are using `array.forEach` with a manual offset, instead of `enumerated`. – Sulthan Apr 05 '20 at 09:15