Working with C Strings in Swift, Or: How to Convert Unsafepointer<Cchar> to Cstring

Working with C strings in Swift, or: How to convert UnsafePointer CChar to CString

Swift 1.1 (or perhaps earlier) has even better C string bridging:

let haystack = "This is a simple string"
let needle = "simple"
let result = String.fromCString(strstr(haystack, needle))

The CString type is gone completely.

Convert Swift string into CChar pointer

If the C function does not mutate the strings passed as arguments,
then the parameters should be declared as const char *,
for example

int func1(const char *s1, const char *s2);

and in that case you can simply pass Swift strings which are automatically converted (compare String value to UnsafePointer<UInt8> function parameter behavior):

let str1 = "first string argument"
let str2 = "second string argument"

let result1 = func1(str1, str2)

If the function parameters are declared as char *

int func2(char *s1, char *s2);

then you have to use withCString() to obtain a temporary representation
of the string as a NUL-terminated array of UTF-8 characters:

let str1 = "first string argument"
let str2 = "second string argument"

// Swift 2:
let result2 = str1.withCString { s1 in
str2.withCString { s2 in
func2(UnsafeMutablePointer(s1), UnsafeMutablePointer(s2))
}
}

// Swift 3:
let result2 = str1.withCString { s1 in
str2.withCString { s2 in
func2(UnsafeMutablePointer(mutating: s1), UnsafeMutablePointer(mutating: s2))
}
}

Note that this still assumes that the function does not mutate the
passed strings, the UnsafeMutablePointer() conversion is only
needed to make the compiler happy.

Does String.init(cString: UnsafePointer Int8 ) copy the memory contents?

The documentation clearly states that the data is copied:

Initializer


init(cString:)


Creates a new string by copying the null-terminated UTF-8 data referenced by the given pointer.


is there a way to convert C strings to swift by referencing the already existing data instead of copying it?

Nope. Strings are frequently copied/destroyed, which involves doing retain/release operations on the underlying buffer, to do the necessary booking keeping of thethe reference count. If the memory is not owned by the String, then there's no way to reliably de-allocate it.

What are you trying to achieve by avoiding the copy?

Convert String to UnsafeMutablePointer char_t in Swift

It all depends on what char_t is.

If char_t converts to Int8 then the following will work.

if let cString = str.cStringUsingEncoding(NSUTF8StringEncoding) {
some_c_func(strdup(cString))
}

This can be collapsed to

some_c_func(strdup(str.cStringUsingEncoding(NSUTF8StringEncoding)!))

WARNING! This second method will cause a crash if func cStringUsingEncoding(_:) returns nil.


Updating for Swift 3, and to fix memory leak

If the C string is only needed in a local scope, then no strdup() is needed.

guard let cString = str.cString(using: .utf8) else {
return
}

some_c_func(cString)

cString will have the same memory lifecycle as str (well similar at least).

If the C string needs to live outside the local scope, then you will need a copy. That copy will need to be freed.

guard let interimString = str.cString(using: .utf8), let cString = strdup(interimString) else {
return
}

some_c_func(cString)

//…

free(cString)

Convert Swift String array to c char**

getDREFSs expects a pointer to an array of optional Int8 pointers.
Also the second argument must be converted to UInt8.

So this would compile:

public func get(drefs: [String]) -> Int {
var cDrefs = [UnsafePointer<Int8>?]()
for dref in drefs {
cDrefs.append(dref.cString(using: .utf8))
}
let result = getDREFs(&cDrefs, UInt8(drefs.count))
return Int(result)
}

But a quick test shows that is does not work if called with
multiple strings. The reason is that the arrays
returned by dref.cString(using: .utf8)
can already be deallocated (and the pointer invalid)
when the C function is called.

Here is a working version, a slight modification of
Convert a Swift Array of String to a to a C string array pointer for
this particular case:

public func get(drefs: [String]) -> Int {
var cargs = drefs.map { UnsafePointer<Int8>(strdup($0)) }
let result = getDREFs(&cargs, UInt8(drefs.count))
for ptr in cargs { free(UnsafeMutablePointer(mutating: ptr)) }
return Int(result)
}

How to convert UnsafePointer CUnsignedChar to String?

struct UnsafePointer<T> has a constructor that takes an unsafe pointer of a different
type as an argument:

/// Convert from a UnsafePointer of a different type.
///
/// This is a fundamentally unsafe conversion.
init<U>(_ from: UnsafePointer<U>)

Therefore you can create a string from ptr : UnsafePointer<CUnsignedChar>
with

let str = String.stringWithCString(UnsafePointer(ptr), encoding: someEncoding)

or, in the case of UTF-8 encoding, more simply with

let str = String.fromCString(UnsafePointer(ptr))

Note that the generic type <CChar> is automatically inferred from the context here,
i.e. the last line is equivalent to

let str = String.fromCString(UnsafePointer<CChar>(ptr))

Convert a Swift Array of String to a to a C string array pointer

You can proceed similarly as in How to pass an array of Swift strings to a C function taking a char ** parameter. It is a bit different because of the different
const-ness of the argument array, and because there is a terminating
nil (which must not be passed to strdup()).

This is how it should work:

let array: [String?] = ["name1", "name2", nil]

// Create [UnsafePointer<Int8>]:
var cargs = array.map { $0.flatMap { UnsafePointer<Int8>(strdup($0)) } }
// Call C function:
let result = command(&cargs)
// Free the duplicated strings:
for ptr in cargs { free(UnsafeMutablePointer(mutating: ptr)) }

Swift conversion of Array of Strings to C array of C Strings not working

That withArrayOfCStrings function works with C functions taking a char * const * argument, i.e. a pointer to constant pointers to characters.

If you change the C declaration to

GDALVectorTranslateOptions* GDALVectorTranslateOptionsNew(char* const* papszArgv,
GDALVectorTranslateOptionsForBinary* psOptionsForBinary)

then it compiles and runs as expected.

If you do not have the option to modify the C code then you can change the helper method to

public func withArrayOfCStrings<R>(_ args: [String],
_ body: (UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>) -> R) -> R {
var cStrings = args.map { strdup($0) }
cStrings.append(nil)

defer {
cStrings.forEach { free($0) }
}

return body(&cStrings)
}

The body is now called with a UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> argument, which corresponds to a C char ** argument.

How do you convert a String to a CString in the Swift Language?

You can get a CString as follows:

import Foundation

var str = "Hello, World"

var cstr = str.bridgeToObjectiveC().UTF8String

EDIT: Beta 5 Update - bridgeToObjectiveC() no longer exists (thanks @Sam):

var cstr = (str as NSString).UTF8String

Passing a Swift string to C

The Swift equivalent of const char * is UnsafePointer<CChar>?, so that's the correct return value. Then you have to think about memory management. One options is do allocate memory for the C string in the Swift function, and leave it to the caller to release the memory eventually:

public func mySwiftFunc(number: Int) -> UnsafePointer<CChar>? {
print("Hello from Swift: \(number)")

let address = "hello there"
let newString = strdup(address)
return UnsafePointer(newString)
}

passes a Swift string to strdup() so that a (temporary) C string representation is created automatically. This C string is then duplicated. The calling C function has to release that memory when it is no longer needed:

int blah() {
const char *retVal = mySwiftFunc(42);
printf("Hello from C: %s\n", retVal);
free((char *)retVal);
return 0;
}

⚠️ BUT: Please note that there are more problems in your code:

  • mySwiftFunc() is an instance method of a class, and therefore has an implicit self argument, which is ignored by the calling C function. That might work by chance, or cause strange failures.
  • @_silgen_name should not be used outside of the Swift standard library, see this discusssion in the Swift forum.
  • A slightly better alternative is @_cdecl but even that is not officially supported. @_cdecl can only be used with global functions.
  • Generally, calling Swift functions directly from C is not officially supported, see this discussion in the Swift forum for the reasons and possible alternatives.


Related Topics



Leave a reply



Submit