The problem that'll be described relates to my previous question: string.withCString and UnsafeMutablePointer(mutating: cstring) wrapped into a function which was my first approach to handle nil Strings (by putting withCString into a function) and to a question which Mecki asked: Why can't I pass an optional Swift String to C function that allows NULL pointers?
Imagine there is a c-function like:
unsigned long randomSign(char *pin, char *tag_signature, char *tag_data, char *xyz);
I know that the function works correctly if I wrap 4 string.withCString closures around the corresponding swift function:
// pin, tag_signature, tag_data and xyz are optional Strings so they may be nil which is a problem for my result.
// corresponding swift function:
// randomSign(pin: UnsafeMutablePointer<Int8>!, tag_signature: UnsafeMutablePointer<Int8>!, tag_data: UnsafeMutablePointer<Int8>!, xyz: UnsafeMutablePointer<Int8>!)
let result = pin.withCString { s1 in return
tag_signature.withCString {s2 in return
tag_data.withCString {s3 in return
xyz.withCString { s4 in return
randomSign(UnsafeMutablePointer(mutating: s1), UnsafeMutablePointer(mutating: s2), UnsafeMutablePointer(mutating: s3), UnsafeMutablePointer(mutating: s4))
}}}}
And so Martin R replied to an easier example, that it is not needed to wrap the closures around randomSign(arguments) and UnsafeMutablePointer(mutating: ...) because it can also take the strings and converts it. But when I drop the closures and use it as Martin R described, it worked at the first launch on the simulator directly after starting the mac, but on consecutive calls of the randomSign-Function the return would tell me that for example the tag_signature or the pin would be invalid (but it actually is valid and I don't know why?!).
This leads me to the problem that I need the withCString closures (at the moment) but I have to handle nil-Strings, which would result the app to crash when it shall return the result because it couldn't evaluate the randomSign-Function.
So I tried to fit the approach below (also suggested by @Martin R) to Swift 3, but I did not workout to adapt it.
//Swift-2 written by Martin R
protocol CStringConvertible {
func withCString<Result>(@noescape f: UnsafePointer<Int8> throws -> Result) rethrows -> Result
}
extension String: CStringConvertible { }
extension Optional where Wrapped: CStringConvertible {
func withOptionalCString<Result>(@noescape f: UnsafePointer<Int8> -> Result) -> Result {
if let string = self {
return string.withCString(f)
} else {
return f(nil)
}
}
}
//Swift 3: ???
If anyone can tell me, why my function only works out when I use withCString but not when I dismiss it, I would be very grateful and also if anyone knows how to solve the issue, i.e. correctly translating the swift-2 code to working swift-3 code.
The problem with
let result = randomSign(UnsafeMutablePointer(mutating: pin),
UnsafeMutablePointer(mutating: tag_signature),
UnsafeMutablePointer(mutating: tag_data),
UnsafeMutablePointer(mutating: xyz))
is that the temporary UTF-8 representation created from the Swift
strings is valid only during each call of UnsafeMutablePointer()
,
but not necessarily still valid during the call of randomSign()
.
(So my final suggestion in https://stackoverflow.com/a/44027397/1187415
was actually not correct, I have updated that part).
A possible Swift 3 version of the wrapper in https://stackoverflow.com/a/39363769/1187415 is
extension Optional where Wrapped == String {
func withOptionalCString<Result>(_ f: (UnsafeMutablePointer<Int8>?) -> Result) -> Result {
if let string = self {
return string.withCString { f(UnsafeMutablePointer(mutating: $0)) }
} else {
return f(nil)
}
}
}
This handles both the optionality and casts the C string pointer
to a mutable pointer (as required by randomSign()
). This can be
called as
let result = pin.withOptionalCString { s1 in
tag_signature.withOptionalCString { s2 in
tag_data.withOptionalCString { s3 in
xyz.withOptionalCString { s4 in
randomSign(s1, s2, s3, s4)
}
}
}
}
Remark: In theory, the problem can be avoided if the signature of randomSign()
is changed to take const char *
parameters:
unsigned long randomSign(const char *pin, const char *tag_signature, const char *tag_data, const char *xyz);
which one could then simply call as
let result = randomSign(pin, tag_signature, tag_data, xyz)
with optional or non-optional Swift strings. However, this does currently not work, as reported in SR-2814 Swift does not correctly pass in multiple optional strings to C function.