2

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.

Community
  • 1
  • 1
n.eesemann
  • 43
  • 4

1 Answers1

1

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.

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Okay first of all I really appreciate all the great effort and work you put into your answers by backing it up with links, examples and explanations. So in practice this means only the theoretical approach (to change it to const char*) that you mentioned wouldn't work due to this bug? But the above extension would work? The extension gets marked red as: "Same-type requirement makes generic parameter 'Wrapped' non-generic" :/ – n.eesemann May 19 '17 at 10:19
  • @n.eesemann: It compiles in my Xcode 8.3.2 (Swift 3.1).  – Yes, the last approach *should* work, but it currently doesn't with more than one optional string, as reported in that bug. – Martin R May 19 '17 at 11:06
  • just updated Xcode and swift, now your suggested extension code is running on my machine too :) . A million thanks, I would upvote your answer, but I lack 4 reputation points! – n.eesemann May 19 '17 at 11:36