cswiftobjective-c-swift-bridge

Passing string from Swift to C back to Swift


I'm seeing some behavior I don't understand when passing strings between Swift and C. Consider the following Swift function:

func demo()
{
    print("\n\n\n\n")                                                            // Line A

    let str = "thisisastring"
    let strptr = UnsafePointer<Int8>(str)        
    let strptr_cstring = String(cString: strptr)
    print("from swift: str = '\(str)'")
    print("from swift: strptr = \(strptr)")
    print("from swift: strptr.pointee = \(strptr.pointee)")
    print("from swift: strptr_cstring = '\(strptr_cstring)'")
    print("from swift: String(cString: strptr) = '\(String(cString: strptr))'")

    let ret_strptr = return_string(str)!                                        // Line B1
    //let ret_strptr = return_string(strptr)!                                   // Line B2
    //let ret_strptr = strptr                                                   // Line B3

    print("from swift: ret_strptr = \(ret_strptr) ")
    print("from swift: ret_strptr.pointee = \(ret_strptr.pointee)")
    let ret_strptr_cstring = String(cString: ret_strptr)
    print("from swift: ret_strptr_cstring = '\(ret_strptr_cstring)'")
    print("from swift: String(cString: ret_strptr) = '\(String(cString: ret_strptr))'")
}

And the C function:

const char *return_string(const char *c)
{
    printf("from c: got pointer %p = %s\n",c,c);
    return c;
}

If I run the demo as it is above (so only lines B2 and B3 are commented out), I get the following output:

from swift: str = 'thisisastring'
from swift: strptr = 0x000000010021b8a0
from swift: strptr.pointee = 116
from swift: strptr_cstring = 'thisisastring'
from swift: String(cString: strptr) = 'thisisastring'
from c: got pointer 0x10021b9b0 = thisisastring
from swift: ret_strptr = 0x000000010021b9b0 
from swift: ret_strptr.pointee = 102
from swift: ret_strptr_cstring = '102m swift: ret_'
from swift: String(cString: ret_strptr) = 'P�!'

Two things surprise me about this: 1) the C code gets pointer 0x10021b9b0 whereas in Swift, the string was stored at 0x000000010021b8a0. I would have expected them to be the same, but I guess Swift makes a copy of the string before passing it to C? 2) much more surprising though is that although the pointer returned from C to Swift is the same location, the contents is different; further, the final two print statements produce different output.

Now, if I simply comment out line A in demo, I get the following results:

from swift: str = 'thisisastring'
from swift: strptr = 0x00000001003894b0
from swift: strptr.pointee = 0
from swift: strptr_cstring = 'S\212' 
from swift: String(cString: strptr) = ''
from c: got pointer 0x100469640 = thisisastring
from swift: ret_strptr = 0x0000000100469640 
from swift: ret_strptr.pointee = 48
from swift: ret_strptr_cstring = '48000010046964'
from swift: String(cString: ret_strptr) = '48000010046964'

Which is surprising because all the stuff before the call to the C function is incorrect.

If I then comment out line B1 and uncomment B3 (just removing the call to C), I get the expected output:

from swift: str = 'thisisastring'
from swift: strptr = 0x00000001004809b0
from swift: strptr.pointee = 116
from swift: strptr_cstring = 'thisisastring'
from swift: String(cString: strptr) = 'thisisastring'
from swift: ret_strptr = 0x00000001004809b0 
from swift: ret_strptr.pointee = 116
from swift: ret_strptr_cstring = 'thisisastring'
from swift: String(cString: ret_strptr) = 'thisisastring'

If I run it with line B2 uncommented (commenting out A, B1, and B3), I get:

from swift: str = 'thisisastring'
from swift: strptr = 0x0000000100203550
from swift: strptr.pointee = 0
from swift: strptr_cstring = '2'
from swift: String(cString: strptr) = ''
from c: got pointer 0x100203550 = 
from swift: ret_strptr = 0x0000000100203550 
from swift: ret_strptr.pointee = 0
from swift: ret_strptr_cstring = ''
from swift: String(cString: ret_strptr) = ''

Finally, if I run with lines A and B2 uncommented (B1 and B3 commented out), I get expected results:

from swift: str = 'thisisastring'
from swift: strptr = 0x00000001005533d0
from swift: strptr.pointee = 116
from swift: strptr_cstring = 'thisisastring'
from swift: String(cString: strptr) = 'thisisastring'
from c: got pointer 0x1005533d0 = thisisastring
from swift: ret_strptr = 0x00000001005533d0 
from swift: ret_strptr.pointee = 116
from swift: ret_strptr_cstring = 'thisisastring'
from swift: String(cString: ret_strptr) = 'thisisastring'

It looks to me like the call across to C is stomping on memory in an unpredictable way, but am I doing something incorrect here?

Running on macOS Sierra, Xcode 8.0, Swift 3.0.


Solution

  • If you want a C string from an NSString, use cString or getCString.