String Value to Unsafepointer≪Uint8≫ Function Parameter Behavior

String value to UnsafePointerUInt8 function parameter behavior

This is working because of one of the interoperability changes the Swift team has made since the initial launch - you're right that it looks like it hasn't made it into the documentation yet. String works where an UnsafePointer<UInt8> is required so that you can call C functions that expect a const char * parameter without a lot of extra work.

Look at the C function strlen, defined in "shims.h":

size_t strlen(const char *s);

In Swift it comes through as this:

func strlen(s: UnsafePointer<Int8>) -> UInt

Which can be called with a String with no additional work:

let str = "Hi."
strlen(str)
// 3

Look at the revisions on this answer to see how C-string interop has changed over time: https://stackoverflow.com/a/24438698/59541

Swift to C bridging: String to UnsafePointerInt8? is not automatically bridged?

The automatic creation of a C string representation from a Swift String is only done when calling a function taking a UnsafePointer<Int8> argument (compare String value to UnsafePointer<UInt8> function parameter behavior), and the C string is only valid for the duration of the function call.

If the C string is only need for a limited lifetime then you can do

let str = "Hello world"
str.withCString { cStringPtr in
var appInfo = VkApplicationInfo()
appInfo.pApplicationName = cStringPtr

// ...
}

For a longer lifetime you can duplicate the string:

let str = "Hello world"
let cStringPtr = strdup(str)! // Error checking omitted for brevity
var appInfo = VkApplicationInfo()
appInfo.pApplicationName = UnsafePointer(cStringPtr)

and release the memory if it is no longer needed:

free(cStringPtr)

UnsafePointerInt8 not working

In

let ua = UnsafePointer<Int8>(a)

the

init(_ other: UnsafePointer<Pointee>)

initializer of UnsafePointer is called. I.e. you are passing
a Swift String to a function taking a UnsafePointer<Int8>
argument. In that case a temporary C string representation is created and passed to the function,
compare String value to UnsafePointer<UInt8> function parameter behavior.

That temporary C string is only valid for the duration of the function
call, and on return, it is not guaranteed that the memory pointed
to by ua still contains that C string (or any valid data). In addition,

let ua = UnsafePointer<Int8>(a)
let ub = UnsafePointer<Int8>(b)
let uc = UnsafePointer<Int8>(c)

can use the same storage for the temporary C string, in that case
the pointers would be equal, as you observed.


If your intention is to call a C function taking const char * arguments, then you can simply pass the Swift strings. The compiler
will insert the necessary code to create C string representations
(which are again valid for the duration of the function call),
as explained in String value to UnsafePointer<UInt8> function parameter behavior:

let a = "aaa"
let b = "bbb"
let c = "ccc"

cfunc(a, b, c)

UnsafeMutablePointerInt8 from String in Swift

The "problem" is that the first two parameters are declared as char *
and not as const char *, even if the strings are not modified by the function:

int dgeev_(char *__jobvl, char *__jobvr, ...);

is mapped to Swift as

func dgeev_(__jobvl: UnsafeMutablePointer<Int8>, __jobvr: UnsafeMutablePointer<Int8>, ...) -> Int32;

A possible workaround is

let result = "N".withCString { 
dgeev_(UnsafeMutablePointer($0), UnsafeMutablePointer($0), &N, ...)
}

Inside the block, $0 is a pointer to a NUL-terminated array of char with the
UTF-8 representation of the string.


Remark: dgeev_() does not modify the strings pointed to by the first two arguments,
so it "should be" declared as

int dgeev_(const char *__jobvl, const char *__jobvr, ...);

which would be mapped to Swift as

func dgeev_(__jobvl: UnsafePointer<Int8>, __jobvr: UnsafePointer<Int8>, ...) -> Int32;

and in that case you could simply call it as

let result = dgeev_("N", "N", &N, ...)

because Swift strings are converted to UnsafePointer<Int8>) automatically,
as explained in String value to UnsafePointer<UInt8> function parameter behavior.

How to pass an array of Swift strings to a C function taking a char ** parameter

The C function

int initialize(int argc, char **argv);

is mapped to Swift as

func initialize(argc: Int32, argv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>) -> Int32

This is a possible solution:

let args = ["-c", "1.2.3.4", "-p", "8000"]

// Create [UnsafeMutablePointer<Int8>]:
var cargs = args.map { strdup($0) }
// Call C function:
let result = initialize(Int32(args.count), &cargs)
// Free the duplicated strings:
for ptr in cargs { free(ptr) }

It uses the fact that in strdup($0)
the Swift string $0 is automatically converted to a C string,
as explained in String value to UnsafePointer<UInt8> function parameter behavior.

Convert an String to an array of int8

A possible solution:

withUnsafeMutablePointer(&myVarOfStructProduct.name) {
strlcpy(UnsafeMutablePointer($0), productName, UInt(sizeofValue(myVarOfStructProduct.name)))
}

Inside the block, $0 is a (mutable) pointer to the tuple. This pointer is
converted to an UnsafeMutablePointer<Int8> as expected by the
BSD library function strlcpy().

It also uses the fact that the Swift string productName is automatically
to UnsafePointer<UInt8>
as explained in String value to UnsafePointer<UInt8> function parameter behavior. As mentioned in the comments in that
thread, this is done by creating a temporary UInt8 array (or sequence?).
So alternatively you could enumerate the UTF-8 bytes explicitly and put them
into the destination:

withUnsafeMutablePointer(&myVarOfStructProduct.name) {
tuplePtr -> Void in
var uint8Ptr = UnsafeMutablePointer<UInt8>(tuplePtr)
let size = sizeofValue(myVarOfStructProduct.name)
var idx = 0
if size == 0 { return } // C array has zero length.
for u in productName.utf8 {
if idx == size - 1 { break }
uint8Ptr[idx++] = u
}
uint8Ptr[idx] = 0 // NUL-terminate the C string in the array.
}

Yet another possible solution (with an intermediate NSData object):

withUnsafeMutablePointer(&myVarOfStructProduct.name) {
tuplePtr -> Void in
let tmp = productName + String(UnicodeScalar(0)) // Add NUL-termination
let data = tmp.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!
data.getBytes(tuplePtr, length: sizeofValue(myVarOfStructProduct.name))
}

Update for Swift 3:

withUnsafeMutablePointer(to: &myVarOfStructProduct.name) {
$0.withMemoryRebound(to: Int8.self, capacity: MemoryLayout.size(ofValue: myVarOfStructProduct.name)) {
_ = strlcpy($0, productName, MemoryLayout.size(ofValue: myVarOfStructProduct.name))
}
}

Cannot convert value of type 'UnsafePointerDouble' to expected argument type 'UnsafePointer_'

So, I was able to solve it, albeit in a roundabout way.

I created a new function convert and used it:

func convertArr<T>(count: Int, data: UnsafePointer<T>) -> [T] {

let buffer = UnsafeBufferPointer(start: data, count: count)
return Array(buffer)
}
...
let doublearrptr = UnsafePointer<Double>(cda)
let arr = convertArr(Int(shobjarrlen), data: doublearrptr)

For some reason this works but not the original syntax...

I'm still open to getting answers from why my original syntax didn't work.



Related Topics



Leave a reply



Submit