I have a problem with the String.withCString{} in combination with UnsafeMutablePointer(mutating: ...).
Given: a C-Function like this
randomSign(xml_string: UnsafeMutablePointer<Int8>!) -> Uint
Also given: a String like
str = "some xml_tag"
My working code is like this (VERSION_1)
func randomSignWrapper_1(xml_tag_str: String) -> Uint {
let result = xml_str.withCString { s in return
randomSign(UnsafeMutablePointer(mutating: s))
}
return result
}
But I want the withCString to be put into a separate function like this:
func testFunction(_ x: String) -> UnsafeMutablePointer<Int8>{
return x.withCString{s in
return UnsafeMutablePointer(mutating: s)
}
}
So that I can easily reuse it (VERSION_2)
func randomSignWrapper_2(xml_tag_str: String) -> Uint {
let result = randomSign(testFunction(xml_tag_str))
return result
}
But the problem is that VERSION_1 delivers the right return value while VERSION_2 is somehow not working properly, and telling me that the data is wrong.
And I would like to know why it behaves like this?
And how to solve it that I can use it the described way?
Check the reference of withCString(_:)
.
Discussion
The withCString(_:) method ensures that the sequence’s lifetime
extends through the execution of body. The pointer argument to body is
only valid for the lifetime of the closure. Do not escape it from the
closure for later use.
Your VERSION_2 code is trying to escape it from the closure for later use. It's not a valid usage of withCString(_:)
.
The region containing UTF-8 representation of the String
is temporal and Swift runtime will release it at any time.
You can write something like this:
func utf8Array(from string: String) -> [Int8] {
return string.cString(using: .utf8)!
}
func randomSignWrapper_3(xml_tag_str: String) -> UInt {
var charArray = utf8Array(from: xml_tag_str)
let result = randomSign(xml_string: &charArray)
return result
}
But I wonder if you need to define a function like utf8Array(from:)
.
withCString { ... }
creates a temporary representation of the Swift
string as null-terminated UTF-8 sequence, which is valid only inside
the closure. Passing the pointer to the outside of the closure is
undefined behaviour.
Also note that you cannot mutate the string using that pointer.
Passing UnsafeMutablePointer(mutating: $0)
only makes the compiler
believe that the pointed-to memory is mutable, but actually doing
so is undefined behaviour and might even crash.
If randomSign()
function does not modify the given
string then the best solution is to change its C declaration to
take a constant string:
unsigned long randomSign(const char *xml_string);
This would be imported to Swift as
func randomSign(_ xml_string: UnsafePointer<Int8>!) -> UInt
Now you can pass a Swift string directly to that function:
let str = "some xml_tag"
let result = randomSign(str)
The compiler automatically inserts the code for creating a
temporary UTF-8 representation and passing that to the function, compare String value to UnsafePointer<UInt8> function parameter behavior.
If you cannot change the C declaration then you still can call it
as
let str = "some xml_tag"
let result = randomSign(UnsafeMutablePointer(mutating: str))
also without the need for your own helper function.
Update: The last suggestion is not correct. The temporary string created by
the compiler is only during for the call of UnsafeMutablePointer()
but not necessarily still valid during the call of randomSign()
.