I'm calling a C library from Swift 4 and I have troubles converting a [String] to const char *[].
The C API defines this method:
int getDREFs(const char* drefs[], unsigned char count);
which is exposed in Swift as
public func getDREFs(_ drefs: UnsafeMutablePointer<UnsafePointer<Int8>?>!, _ count: UInt8) -> Int32
The Swift wrapper I'm trying to write is the following
public func get(drefs: [String]) throws {
var cDrefs = [UnsafePointer<Int8>]()
for dref in drefs {
cDrefs.append(dref.cString(using: .utf8)!)
}
let pDrefs = UnsafeMutablePointer<UnsafePointer<Int8>>(&cDrefs)
getDREFFs(pDrefs, drefs.count)
}
but the error I get is
Cannot convert value of type 'UnsafeMutablePointer<UnsafePointer<Int8>>' to expected argument type 'UnsafeMutablePointer<UnsafePointer<Int8>?>!'
what am I missing?
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)
}
Related
This question already has answers here:
How to pass an array of Swift strings to a C function taking a char ** parameter
(2 answers)
Closed 2 years ago.
Some C functions expect a char ** argument, like int main(int argc, char **argv). While Swift can automatically handle Char *, it doesn't handle char **. What's the best way to call such a function?
There are several "withXXX" methods around, but these require closures and get messy when there are multiple arrays. Still others use a separate "dealloc" function you must call when done. This goes against the Swift way. However, using a class just to get a deinit opportunity is wrong.
This class provides a pointer that works with char** and automatically deallocates the memory, even though it's a struct (using a little trick with a mapped data with deallocator).
public struct CStringArray {
public let pointer: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>
public let count: Int
private var data: Data
public init(_ array: [String]) {
let count = array.count
// Allocate memory to hold the CStrings and a terminating nil
let pointer = UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>.allocate(capacity: count + 1)
pointer.initialize(repeating: nil, count: count + 1) // Implicit terminating nil at the end of the array
// Populate the allocated memory with pointers to CStrings
for (e, s) in array.enumerated() {
pointer[e] = strdup(s)
}
// This uses the deallocator available on the data structure as a solution to the fact that structs do not have `deinit`
self.data = Data(bytesNoCopy: pointer, count: MemoryLayout<UnsafeMutablePointer<CChar>>.size * count, deallocator: .custom({_,_ in
for i in 0...count - 1 {
free(pointer[i])
}
pointer.deallocate()
}))
self.pointer = pointer
self.count = array.count
}
public subscript(index: Data.Index) -> UnsafeMutablePointer<CChar>? {
get {
precondition(index >= 0 && index < count, "Index out of range")
return pointer[index]
}
}
public subscript(index: Data.Index) -> String? {
get {
precondition(index >= 0 && index < count, "Index out of range")
if let pointee = pointer[index] {
return String(cString: pointee)
}
return nil
}
}
}
I'm trying to pass a const char * to an old C library converted from a Swift string in Swift.
This is the C function I'm calling:
artnet_node artnet_new(const char *ip, int verbose) { ...
how can I convert a Swift string to this const char type? It works when I pass ipAddress like this:
internal var ipAddress = "192.168.1.43"
but dit does not work when I pass it like this
internal var ipAddress:String = "192.168.1.43"
I need this in a function where I need to specify the type:
internal func setupArtnode(ip:String) -> Int{
I tried using AnyObject instead of String but that doesn't work either.
Thanks.
You should be able to pass a String directly to a C function expecting const char * and it will be automatically converted to a null-terminated UTF-8 string:
let string = "string"
let node = artnet_new(string, 1)
See Interacting with C APIs for more information. Here is the relevant excerpt:
When a function is declared as taking an UnsafePointer argument,
it can accept any of the following:
A String value, if Type is Int8 or UInt8. The string will automatically be converted to UTF8 in a buffer, and a pointer to that
buffer is passed to the function.
Not sure why but this code is working. This passes a string to a C function expecting a const char* which seems to be the same as a unsafePointer.
internal func setupArtnode(ipAddress:String) -> NSInteger{
let cString = self.ipAddress.cString(using: String.defaultCStringEncoding)!
let newString:String = NSString(bytes: cString, length: Int(ipAddress.characters.count), encoding:String.Encoding.ascii.rawValue)! as String
let key2Pointer = UnsafePointer<Int8>(newString)
node = artnet_new(key2Pointer, Int32(verbose)) // VERBOSE : true(1) , false(0)
...
Simple way for Swift 3
var ipAddress: String = "192.168.1.43"
var verbose: Int = 1
artnet_node artnet_new((ipAddress as NSString).utf8String, verbose)
You didn't specify what your Swift array contains. In any case, you need to convert your Swift array to an array of Int8:
let str = "Hello world"
let cArray = str.cString(using: .utf8)
artnet_new(cArray, 1)
I have the following C struct:
typedef struct {
char** categories;
int category_size;
} category_fmc_s_type;
My Swift array has the following values:
let categories = ["Weekday", "Weekend"]
I want to populate the C Struct field 'categories' with 'Weekday' & 'Weekend'. To do this I call my toPointer():
fileprivate static func toPointer(_ args: [String]) -> UnsafeMutablePointer<UnsafeMutablePointer<Int8>> {
let buffer = UnsafeMutablePointer<UnsafeMutablePointer<Int8>>.allocate(capacity: args.count)
for (index, value) in args.enumerated() {
buffer[index] = UnsafeMutablePointer<Int8>(mutating: (value as NSString).utf8String!)
}
return buffer
}
I keep getting the following XCode 8 error:
Cannot convert value of type 'UnsafeMutablePointer<UnsafeMutablePointer<Int8>>' to expected argument type 'UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>!'
Any suggestions? I don't understand why there is the optional and '!' in the C-Struct definition implicitly.
As the compiler emits as an error, you need to unwrap after Int8 w/ "?" as follows.
fileprivate func toPointer(_ args: [String]) -> UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> {
let buffer = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: args.count)
for (index, value) in args.enumerated() {
buffer[index] = UnsafeMutablePointer<Int8>(mutating: (value as NSString).utf8String!)
}
return buffer
}
then,
func testMyCat() {
let categories = ["Weekday", "Weekend"]
let buffer = toPointer(categories)
var mycat = category_fmc_s_type()
mycat.categories = buffer // you would see compile error w/o "?"
}
the code above works w/o error. Martin's solution gives a compile error at
mycat.categories = &cargs (see the link)
I don't know why.
Check the reference of utf8String property of NSString:
Discussion
This C string is a pointer to a structure inside the string object,
which may have a lifetime shorter than the string object and will
certainly not have a longer lifetime. Therefore, you should copy the
C string if it needs to be stored outside of the memory context in
which you use this property.
The term memory context is not well-defined, but one thing sure is that you cannot expect the allocated region for the C string would live forever. When the member categories in the category_fmc_s_type is accessed, the pointers may be pointing to the already freed regions.
Applying the suggestion from Martin R to your code, your code would be like this:
fileprivate static func toPointer(_ args: [String]) -> UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> {
let buffer = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: args.count)
buffer.initialize(from: args.lazy.map{strdup($0)})
return buffer
}
And remember, after you finish using the category_fmc_s_type, you need to deallocate the regions allocated by strdup(_:) and UnsafeMutablePointer.allocate(capacity:):
fileprivate static func freePointer(_ pointers: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>, count: Int) {
for i in 0..<count {
free(pointers[i])
}
pointers.deinitialize(count: count)
pointers.deallocate(capacity: count)
}
Assuming a function that operates on any Array:
func g<T>(array: [T]) -> Void {
// ...
}
...and a function that receives a value of type Any:
func f(x: Any) -> Void {
if x is Array {
// g(?)
}
}
How can I get a properly typed version of x that I can pass to g?
Note that g doesn't rely (explicitly) on the type T. All it needs is the ability to iterate over the elements, which can be treated as values of type Any. Any solution that rewrites g to remove the type parameter is therefore also acceptable.
Edit: to make things harder, I'd like this to work on Linux, where Swift arrays aren't NSArrays until you've called .bridge() on them.
Warning Please note that the below results are based on my experiments on Mirror and work in Swift 2.1.1 (Xcode 7.1.1). It might be that future Swift versions will change the Mirror behaviour.
If finding out dynamically if the value is an array of any type, you could use Swift's reflection support, via the Mirror struct:
func processArray(array: Any) -> Bool {
let mirror = Mirror(reflecting: array)
guard mirror.displayStyle == .Collection else {
return false
}
print("array has \(mirror.children.count) elements")
return true
}
func f(x: Any) {
if processArray(x) {
// i just found an array
}
}
f([1, 2, 3, "4"]) // prints "array has 4 elements"
if you want to keep the generic, you could do this instead:
func g<T>(array: [T]) -> Void {
}
func f(x: Any) -> Void {
if let array = x as? [Any] {
g(array)
}
}
I have a Swift function that accepts Any and I want it to be able to accept an array of Strings, an array of Ints, a mixed array, or an array of arrays, etc. It also can accept just a String or an Int, etc, not in an array.
So I have this:
private func parse(parameter: Any) {
if parameter is Int {
// Int
} else if (parameter is Float) || (parameter is Double) {
// Double
} else if parameter is String {
// String
} else if parameter is Bool {
// Bool
} else if let array = parameter as? [Any] {
// Should catch all Arrays
} else {
assert(false, "Unsupported type") // [String] ends up here
}
}
But if I call parse(["Strings"]), the assert is raised. How can I catch all types of Arrays?
edit - there was some confusion as to what I'm trying to accomplish. I basically need to return a String based on the type, so Int -> "" and String -> "", so an array would make recursive calls to return "..."
This post is marked as a duplicate, but that other question is about Javascript, not Swift.
I finally found the way to do that, which is to use NSArray for casting.
private func parse(x: Any) {
if let o = x as? [Any] {
println("[Any]")
}
if let o = x as? [AnyObject] {
println("[AnyObject]")
}
if let o = x as? NSArray {
println("NSArray")
}
}
let a: [Any] = ["bar"]
let b: [AnyObject] = ["bar"]
let c = ["foo", 3.14]
parse(a) // ==> [Any]
parse(b) // ==> [AnyObject], and also NSArray
parse(c) // ==> NSArray
It look so that an array containing values of Any internally represented in NSArray.
(But should it be able to cast c to [Any]...? I'm suspecting it's a bug.)
The key to understanding typing and type related issues in Swift is that all roads lead to protocols.
The challenge of this problem is detecting any type of array, not just one concrete type. The OP's example failed because [Any] is not a base class or a generalized pattern of [String], that is to say, that (from what I can tell), in Swift [T] is not covariant on T. Beyond that, you cannot check for SequenceType or CollectionType since they have associated types (Generator.Element).
The idiomatic solution is thus to use a marker protocol to indicate which types you want to match your criteria. As illustrated below, you achieve this by creating an empty protocol, and associating it with the types of interest.
import Foundation
protocol NestedType {}
extension Array: NestedType {}
extension Set: NestedType {}
extension Dictionary: NestedType {}
extension NSSet: NestedType {}
protocol AnyTypeOfArray {}
extension Array: AnyTypeOfArray {}
extension NSArray: AnyTypeOfArray {}
protocol AnyTypeOfDictionary {}
extension Dictionary: AnyTypeOfDictionary {}
func printType(v:Any) {
if v is NestedType {
print("Detected a nested type")
}
if v is AnyTypeOfArray {
print("\t which is an array")
}
else if v is AnyTypeOfDictionary {
print("\t which is a dictionary")
}
}
printType([String:Int]())
printType([Int]())
printType(NSArray())
The output of which is:
Detected a nested type
which is a dictionary
Detected a nested type
which is an array
Detected a nested type
which is an array
One way you can do this is to separate the function out to two separate implementations (with the same name), one that takes anArray and one for everything else. You'll also need to make them generic functions instead of using the Any type. With that setup, Swift can use type inference to figure out the best function to call.
I'd implement it something like this (I'm just printlning the type to show where things end up):
func parse<T>(parameter: T) {
if parameter is Int {
println("Int")
} else if (parameter is Float) || (parameter is Double) {
println("Double")
} else if parameter is String {
println("String")
} else if parameter is Bool {
println("Bool")
} else {
assert(false, "Unsupported type")
}
}
func parse<T>(parameter: Array<T>) {
println("Array")
for element in parameter {
// Recursively parsing...
parse(element)
}
}
Then calling it like this:
parse(1) // Int
parse(0.1) // Double
parse("asdf") // String
parse(true) // Bool
parse(["asdf", "asdf"]) // Array -> String String
Outputs:
Int
Double
String
Bool
Array
String
String
You can use the _stdlib_getTypeName that returns the mangled type name for the given value.
For example:
var myString = "String"
var myInteger = 10
var myArray = [10,22]
var myDictionary = ["one": 1, "two": 2, "three": 3]
println("\(_stdlib_getTypeName(myString))")
println("\(_stdlib_getTypeName(myInteger))")
println("\(_stdlib_getTypeName(myArray))")
println("\(_stdlib_getTypeName(myDictionary))")
The result will be:
_TtSS // for String
_TtSi // for integer
_TtSa // for array
_TtVSs10Dictionary // for dictionary