Search code examples
cswiftinteropunsafe-pointers

String.withCString when the String is nil


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.


Solution

  • 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.