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 differentconst
-ness of the argument array, and because there is a terminatingnil
(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 implicitself
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
Swift - Cast Int64 to Anyobject for Nsmutablearray
Swift Ternary Operator Compilation Error
How to Read a File in a Swift Playground
How to Declare That a Computed Property 'Throws' in Swift
Difference Between Force Unwrapping Optionals and Implicitly Unwrapped Optionals
Swift: Optional Text in Optional Value
How Replace Position++ Code to Make It Swift 3 Compatible
Spacer Not Working with Form Inside a VStack
How to Remove Optional from String Value Swift
Convert a Custom Object to Data to Be Saved in Nsuserdefauts
How to Set Top Left and Right Corner Radius with Desired Drop Shadow in Uitabbar
Non-Strong References Not Working in Playground
Showing Cells in Demands in Uicollectionview with Vertical Infinite Scroll