Skip to content

Instantly share code, notes, and snippets.

@neilpa
Last active August 7, 2020 02:24
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neilpa/b430d148d1c5f4ae5ddd to your computer and use it in GitHub Desktop.
Save neilpa/b430d148d1c5f4ae5ddd to your computer and use it in GitHub Desktop.
Swift wrappers for C functions taking char** arguments
// Usage
let argv = CStringArray(["ls", "/"])
posix_spawnp(nil, argv.pointers[0], nil, nil, argv.pointers, nil)
// Is this really the best way to extend the lifetime of C-style strings? The lifetime
// of those passed to the String.withCString closure are only guaranteed valid during
// that call. Tried cheating this by returning the same C string from the closure but it
// gets dealloc'd almost immediately after the closure returns. This isn't terrible when
// dealing with a small number of constant C strings since you can nest closures. But
// this breaks down when it's dynamic, e.g. creating the char** argv array for an exec
// call.
class CString {
private let _len: Int
let buffer: UnsafeMutablePointer<Int8>
init(_ string: String) {
(_len, buffer) = string.withCString {
let len = Int(strlen($0) + 1)
let dst = strcpy(UnsafeMutablePointer<Int8>.alloc(len), $0)
return (len, dst)
}
}
deinit {
buffer.dealloc(_len)
}
}
// An array of C-style strings (e.g. char**) for easier interop.
class CStringArray {
// Have to keep the owning CString's alive so that the pointers
// in our buffer aren't dealloc'd out from under us.
private let _strings: [CString]
let pointers: [UnsafeMutablePointer<Int8>]
init(_ strings: [String]) {
_strings = strings.map { CString($0) }
pointers = _strings.map { $0.buffer }
// NULL-terminate our string pointer buffer since things like
// exec*() and posix_spawn() require this.
pointers.append(nil)
}
}
@helje5
Copy link

helje5 commented Mar 12, 2015

Maybe we could hang on to the closures somehow? Something like:

var mm = [ (UnsafePointer) : UnsafePointer ]()
let a = { cstr in ..., mm[a] = cstr; }
str1.withCString(a)
str2.withCString(a)

Just an idea, didn't try. Though I guess the pointer is not necessarily 'retained' by the closure, but free'd externally after the closure invocation. Hm, maybe still worth a try ;-)

@KingOfBrian
Copy link

Hey -- thanks, this was very helpful! I think you could get rid of the len state if you wanted to by changing the deinit to:

    deinit {
        buffer.dealloc(Int(strlen(buffer) + 1))
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment