Last active
August 7, 2020 02:24
-
-
Save neilpa/b430d148d1c5f4ae5ddd to your computer and use it in GitHub Desktop.
Swift wrappers for C functions taking char** arguments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} | |
} |
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
Maybe we could hang on to the closures somehow? Something like:
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 ;-)