Why does Swift return an unexpected pointer when converting an optional String into an UnsafePointer? - swift

I noticed some unusual behaviour when working with a C library which took strings in as constĀ charĀ * (which is converted to Swift as UnsafePointer<Int8>!); passing a String worked as expected, but a String? seemed to corrupt the input. Consider the test I wrote:
func test(_ input: UnsafePointer<UInt8>?) {
if let string = input {
print(string[0], string[1], string[2], string[3], string[4], string[5])
} else {
print("nil")
}
}
let input: String = "Hello"
test(input)
This works as expected, printing a null-terminated list of UTF-8 bytes for the input string: 72 101 108 108 111 0
However, if I change the input to an optional string, so that it becomes:
let input: String? = "Hello"
I get a completely different set of values in the result (176 39 78 23 1 0), even though I would expect it to be the same. Passing in nil works as expected.
The C library's function allows NULL in place of a string, and I sometimes want to pass that in in Swift as well, so it makes sense for the input string to be an optional.
Is this a bug in Swift, or was Swift not designed to handle this case? Either way, what's the best way to handle this case?
Edit
It appears to have something to do with multiple arguments. The C function:
void multiString(const char *arg0, const char *arg1, const char *arg2, const char *arg3) {
printf("%p: %c %c %c\n", arg0, arg0[0], arg0[1], arg0[2]);
printf("%p: %c %c %c\n", arg1, arg1[0], arg1[1], arg1[2]);
printf("%p: %c %c %c\n", arg2, arg2[0], arg2[1], arg2[2]);
printf("%p: %c %c %c\n", arg3, arg3[0], arg3[1], arg3[2]);
}
Swift:
let input0: String? = "Zero"
let input1: String? = "One"
let input2: String? = "Two"
let input3: String? = "Three"
multiString(input0, input1, input2, input3)
Results in:
0x101003170: T h r
0x101003170: T h r
0x101003170: T h r
0x101003170: T h r
It appears that there's a bug with how Swift handles multiple arguments.

I didn't find anything useful on if this is desired behaviour or just a bug.
The pragmatic solution would probably be to just have a proxy method like this, but you probably did something similar already.
func proxy(_ str: String?, _ functionToProxy: (UnsafePointer<UInt8>?) -> ()) {
if let str = str {
functionToProxy(str)
} else {
functionToProxy(nil)
}
}
proxy(input, test)
Did you test if it was working in Swift 2? They changed something maybe related in Swift 3:
https://github.com/apple/swift-evolution/blob/master/proposals/0055-optional-unsafe-pointers.md

Just to be clear, there is a workaround until Apple fixes this. Unwrap your optional Strings before passing them and everything will work fine.
var anOptional: String?
var anotherOptional: String?
func mySwiftFunc() {
let unwrappedA = anOptional!
let unwrappedB = anotherOptional!
myCStringFunc(unwrappedA, unwrappedB)
}

As mentioned in the comments, this is a clear bug in Swift.
Here's a workaround I'm using. If you can't trust Swift to convert the strings to pointers for you, then you've got to do it yourself.
Assuming C function defined as:
void multiString(const char *arg0, const char *arg1, const char *arg2);
Swift code:
func callCFunction(arg0: String?, arg1: String?, arg2: String?) {
let dArg0 = arg0?.data(using: .utf8) as NSData?
let pArg0 = dArg0?.bytes.assumingMemoryBound(to: Int8.self)
let dArg1 = arg1?.data(using: .utf8) as NSData?
let pArg1 = dArg1?.bytes.assumingMemoryBound(to: Int8.self)
let dArg2 = arg2?.data(using: .utf8) as NSData?
let pArg2 = dArg2?.bytes.assumingMemoryBound(to: Int8.self)
multiString(pArg1, pArg2, pArg3)
}
Warning:
Don't be tempted to put this in a function like:
/* DO NOT USE -- BAD CODE */
func ocstr(_ str: String?) -> UnsafePointer<Int8>? {
guard let str = str else {
return nil
}
let nsd = str.data(using: .utf8)! as NSData
//This pointer is invalid on return:
return nsd.bytes.assumingMemoryBound(to: Int8.self)
}
which would remove repeated code. This doesn't work because the data object nsd gets deallocated at the end of the function. The pointer is therefore not valid on return.

Related

Converting a String to UnsafeMutablePointer<UInt16>

I'm trying to use a library which was written in C. I've imported .a and .h files at Xcode project, and checked it works properly. I've already made them working on Objective-C, and now for Swift.
A problem I've got is functions' arguments. There's a function requires an argument widechar(defined as typedef Unsigned short int in Library), which was UnsafeMutablePointer<UInt16> in Swift. The function translates it and return the result.
So I should convert a String to UnsafeMutablePointer<UInt16>. I tried to find the right way to converting it, but I've only got converting it to UnsafeMutablePointer<UInt8>. I couldn't find answer/information about converting String to UnsafeMutablePointer<UInt16>.
Here's a source code I've written.
extension String{
var utf8CString: UnsafePointer<Int8> {
return UnsafePointer((self as NSString).utf8String!)
}
}
func translate(toBraille: String, withTable: String) -> [String]? {
let filteredString = toBraille.onlyAlphabet
let table = withTable.utf8CString
var inputLength = CInt(filteredString.count)
var outputLength = CInt(maxBufferSize)
let inputValue = UnsafeMutablePointer<widechar>.allocate(capacity: Int(outputLength))
let outputValue = UnsafeMutablePointer<widechar>.allocate(capacity: Int(outputLength))
lou_translateString(table, inputValue, &inputLength, outputValue, &outputLength, nil, nil, 0)
//This is a function that I should use.
let result:[String] = []
return result
}
You have to create an array with the UTF-16 representation of the Swift
string that you can pass to the function, and on return create
a Swift string from the UTF-16 array result.
Lets assume for simplicity that the C function is imported to Swift as
func translateString(_ source: UnsafeMutablePointer<UInt16>, _ sourceLen: UnsafeMutablePointer<CInt>,
_ dest: UnsafeMutablePointer<UInt16>, _ destLen: UnsafeMutablePointer<CInt>)
Then the following should work (explanations inline):
// Create array with UTF-16 representation of source string:
let sourceString = "Hello world"
var sourceUTF16 = Array(sourceString.utf16)
var sourceLength = CInt(sourceUTF16.count)
// Allocate array for UTF-16 representation of destination string:
let maxBufferSize = 1000
var destUTF16 = Array<UInt16>(repeating: 0, count: maxBufferSize)
var destLength = CInt(destUTF16.count)
// Call translation function:
translateString(&sourceUTF16, &sourceLength, &destUTF16, &destLength)
// Create Swift string from UTF-16 representation in destination buffer:
let destString = String(utf16CodeUnits: destUTF16, count: Int(destLength))
I have assumed that the C function updates destLength to reflect
the actual length of the translated string on return.

Trouble converting a string to an Int

The following works in Playground:
func stringToInt(numberStr: String!) -> Int {
print(numberStr)
return Int(numberStr)!
}
let strNum1: String?
strNum1 = "1"
let result = stringToInt(numberStr: strNum1)
It returns 1 as expected.
In Xcode, a similar approach fails:
func stringToInt(numberStr: String!) -> Int {
print("\(numberStr!)")
let str = "\(numberStr!)"
print(Int(str))
return Int(str)!
}
The first print produces: Optional(1)
The second print produces: nil
The return statement fails because it is attempting to create an Int from a nil.
It must be something simple but I haven't been able to determine why it's not working. This is in Swift 3 and Xcode 8 BTW.
#Hamish:
In Xcode, I have a string with a numeric value. This:
print("number: (selectedAlertNumber) - unit: (selectedAlertUnit)")
...produces this:
number: Optional(1) - unit: Day
Then, I'm checking to see if either selectedAlertNumber of selecterAlertUnit != "-"
if selectedAlertNumber != "-" && selectedAlertUnit != "-" {
// set alert text
var unitStr = selectedAlertUnit
let alertNumber = stringToInt(numberStr: selectedAlertNumber)
if alertNumber > 1 {
unitStr.append("s")
}
let alertText = "...\(selectedAlertNumber) \(unitStr) before event."
alertTimeCell.setAlertText(alertText: alertText)
// set alert date/time
}
The let alertNumber = stringToInt... line is how I'm calling the function. I could just attempt the conversion there but I wanted to isolate the problem by wrapping the conversion in it's own function.
Using string interpolation to convert values to a String is usually not advised since the output may differ depending on optional status of the value. For example, consider these two functions:
func stringToInt(numberStr: String!) -> Int
{
print("\(numberStr!)")
let str = "\(numberStr!)"
return Int(str)!
}
func otherStringToInt(numberStr: String!) -> Int
{
print(numberStr)
let str = "\(numberStr)"
return Int(str)!
}
The only difference between these two is the ! in the second function when using string interpolation to get a String type value from numberStr. To be more specific, at the same line in function 1 compared to function 2, the string values are very different depending on whether or not the interpolated value is optional:
let str1: String = "1"
let str2: String! = "1"
let str3: String? = "1"
let otherStr1 = "\(str1)" // value: "1"
let otherStr2 = "\(str2)" // value: "Optional(1)"
let otherStr3 = "\(str2!)" // value: "1"
let otherStr4 = "\(str3)" // value: "Optional(1)"
let otherStr5 = "\(str3!)" // value: "1"
Passing otherStr2 or otherStr4 into the Int initializer will produce nil, since the string "Optional(1)" is not convertible to Int. Additionally, this will cause an error during the force unwrap. Instead of using string interpolation in your function, it would be better to just use the value directly since it's already a String.
func stringToInt(numberStr: String!) -> Int
{
return Int(numberStr)!
}
Let me know if this makes sense.
Also, my own personal feedback: watch out force unwrapping so frequently. In many cases, you're running the risk of getting an error while unwrapping a nil optional.

Swift3: Proper way to convert string to null-terminated C-string

I am interfacing with libxml2 in swift, and the C APIs binding (still) produce UnsafePointer<Int8>! for c-strings. Whereas Swift APIs normally result in UnsafePointer<UInt8>!.
So my question is - am I doing the string to null-terminated C-string in a proper way?
let cfilePath = unsafeBitCast(myStringString.nulTerminatedUTF8.withUnsafeBufferPointer { $0.baseAddress }, to: UnsafePointer<Int8>.self)
Should I instead prefer using some other method instead of just bypassing Swift type checking with interpreting UInt8 bytes as Int8 bytes?
I'm not sure this solves your problem exactly but for a project where I am sending strings over bluetooth this did the trick:
extension String {
var nullTerminated: Data? {
if var data = self.data(using: String.Encoding.utf8) {
data.append(0)
return data
}
return nil
}
}
Use like this
let data = "asfasf".nullTerminated
I can't find the function the other answers are referencing: nulTerminatedUTF8. Maybe it already does this.
don't use unsafeBitCast for that!!
let cstr = "alpha".nulTerminatedUTF8
let int8arr = cstr.map{ Int8(bitPattern: $0) }
let uint8arr = Array(cstr)
print(int8arr.dynamicType, uint8arr.dynamicType)
// Array<Int8> Array<UInt8>
update
let uint8: UInt8 = 200
let int8 = Int8(bitPattern: uint8)
print(uint8, int8)
// 200 -56

User input in swift toInt() returning nil

i'm trying to create a simple user input but the only function that i have found in swift is this one
func input() -> String {
var keyboard = NSFileHandle.fileHandleWithStandardInput()
var inputData = keyboard.availableData
return NSString(data: inputData, encoding:NSUTF8StringEncoding) as String!
}
then i'm trying to convert input() to an Int for a mathematic operation (i'm using 1 as the input) with this
var inputToInt = input().toInt()!
in this point i get only nil i don't know what to do.
Swift 2.0 has a function called readLine() - using it is a much better idea than rolling your own.
http://swiftdoc.org/swift-2/func/readLine/
Your conversion to Int fails because the string contains newline. You can use this to clean it up:
let s = NSString(data: inputData, encoding:NSUTF8StringEncoding) as String!
if s.hasSuffix("\n") {
return s.substringToIndex(x.endIndex.predecessor())
} else {
return s
}

Swift - converting from UnsafePointer<UInt8> with length to String

I considered a lot of similar questions, but still can't get the compiler to accept this.
Socket Mobile API (in Objective-C) passes ISktScanDecodedData into a delegate method in Swift (the data may be binary, which I suppose is why it's not provided as string):
func onDecodedData(device: DeviceInfo?, DecodedData d: ISktScanDecodedData?) {
let symbology: String = d!.Name()
let rawData: UnsafePointer<UInt8> = d!.getData()
let rawDataSize: UInt32 = decoded!.getDataSize()
// want a String (UTF8 is OK) or Swifty byte array...
}
In C#, this code converts the raw data into a string:
string s = Marshal.PtrToStringAuto(d.GetData(), d.GetDataSize());
In Swift, I can get as far as UnsafeArray, but then I'm stuck:
let rawArray = UnsafeArray<UInt8>(start: rawData, length: Int(rawDataSize))
Alternatively I see String.fromCString and NSString.stringWithCharacters, but neither will accept the types of arguments at hand. If I could convert from UnsafePointer<UInt8> to UnsafePointer<()>, for example, then this would be available (though I'm not sure if it would even be safe):
NSData(bytesNoCopy: UnsafePointer<()>, length: Int, freeWhenDone: Bool)
Is there an obvious way to get a string out of all this?
This should work:
let data = NSData(bytes: rawData, length: Int(rawDataSize))
let str = String(data: data, encoding: NSUTF8StringEncoding)
Update for Swift 3:
let data = Data(bytes: rawData, count: Int(rawDataSize))
let str = String(data: data, encoding: String.Encoding.utf8)
The resulting string is nil if the data does not represent
a valid UTF-8 sequence.
How about this, 'pure' Swift 2.2 instead of using NSData:
public extension String {
static func fromCString
(cs: UnsafePointer<CChar>, length: Int!) -> String?
{
if length == .None { // no length given, use \0 standard variant
return String.fromCString(cs)
}
let buflen = length + 1
var buf = UnsafeMutablePointer<CChar>.alloc(buflen)
memcpy(buf, cs, length))
buf[length] = 0 // zero terminate
let s = String.fromCString(buf)
buf.dealloc(buflen)
return s
}
}
and Swift 3:
public extension String {
static func fromCString
(cs: UnsafePointer<CChar>, length: Int!) -> String?
{
if length == nil { // no length given, use \0 standard variant
return String(cString: cs)
}
let buflen = length + 1
let buf = UnsafeMutablePointer<CChar>.allocate(capacity: buflen)
memcpy(buf, cs, length)
buf[length] = 0 // zero terminate
let s = String(cString: buf)
buf.deallocate(capacity: buflen)
return s
}
}
Admittedly it's a bit stupid to alloc a buffer and copy the data just to add the zero terminator.
Obviously, as mentioned by Zaph, you need to make sure your assumptions about the string encoding are going to be right.