Reputation: 1226
I’m writing Swift code that interfaces with a C library. The library exposes an incomplete struct, which I’m wrapping in a Swift class. The initializer function for this struct takes a char*
argument, or UnsafeMutablePointer<UInt8>
in Swift. The library doesn’t provide a way to assign a different pointer later.
I’m trying to figure out how to get an UnsafeMutablePointer<UInt8>
to a Swift string, with two main requirements. First, the string should be mutable without changing the pointer. Second, the pointer (and String)’s lifetime should be as long as the wrapper object's.
Is this possible, and if so, how?
Upvotes: 1
Views: 584
Reputation: 539685
A pointer to a C string representation of a Swift string is obtained with
s.withCString { cStringPtr in
callCFunction(cStringPtr)
}
but there are two problems: First, the storage pointed to by the pointer is not mutable. If the C function is declared as taking a char *
but does not actually mutate the string (i.e. if it should have been declared as taking a const char *
argument) then you can make the pointer mutable with
s.withCString { cStringPtr in
let mutableCStringPtr = UnsafeMutablePointer(mutating: cStringPtr)
callCFunction(mutableCStringPtr)
}
But this pointer still refers to the same (immutable) storage, i.e. this causes undefined behavior if the C function mutates the C string.
The second problem with this approach is that the pointer is only valid for the execution of the closure. You cannot take the pointer and save it for later usage. This would be undefined behavior:
let mutablePointer = s.withCString { cStringPtr in
UnsafeMutablePointer(mutating: cStringPtr)
}
// Use `mutablePointer` later.
For a longer lifetime (i.e. the lifetime of the wrapper instance) one must allocate memory and copy the C string. This can for example be done with strdup()
:
class Wrapper {
var cStringPtr: UnsafeMutablePointer<CChar>
init(s: String) {
guard let cStringPtr = strdup(s) else {
// Handle "no memory" error ...
}
self.cStringPtr = cStringPtr
}
deinit {
free(cStringPtr)
}
}
(If you wonder why a Swift string can be passed to strdup()
directly, see String value to UnsafePointer<UInt8> function parameter behavior.)
Upvotes: 5