string.withCString and UnsafeMutablePointer(mutati

2019-07-25 09:56发布

问题:

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?

回答1:

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:).



回答2:

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().