0

I am having trouble converting an array of Swift Strings into an array for a c function with the signature:

PGconn *PQconnectStartParams(const char * const *keywords, const char * const *values, int expand_dbname)

In Swift, the const char * const * shows up as:

<UnsafePointer<UnsafePointer<Int8>>

So I try to convert the contents of a Dictionary [String:String] called 'options' and feed that to the function as follows:

var keys = [[Int8]]()
var values = [[Int8]]()
for (key, value) in options {
    var int8Array = key.cStringUsingEncoding(NSUTF8StringEncoding)!
    keys.append(int8Array)
    int8Array = value.cStringUsingEncoding(NSUTF8StringEncoding)!
            values.append(int8Array)
}
pgConnection = PQconnectStartParams(UnsafePointer(keys), UnsafePointer(values), 0)

It compiles and runs, but the function does not work.

Any insight would be greatly appreciated.

6
  • Ok right away: you are talking about a function named PQconnectdbParams() yet you are calling a function named PQconnectStartParams()...? Commented Apr 22, 2016 at 2:37
  • Take a look at const char * const *keywords. keywords is a pointer to a const pointer to a const char (which is the same as char const, both mean "constant character"). This means you are not looking for a single string ("null terminated character array"), yet you probably are looking for an array of strings. Kind of like char **argv. I don't know much about Swift but you'd have to figure out how to convert a Swift string into a C null-terminated character array, and then have to find out how to have an array of those convert into a C array of strings, I think. Commented Apr 22, 2016 at 2:45
  • Sorry. Clerical error. Was looking at the wrong part of the documentation. Fixed the function signature and you're right, I'm not looking for a null terminated array. I need and array of cstrings. Which is what I thought I was generating in my code. Commented Apr 22, 2016 at 2:49
  • Ok, it looks like you are trying to generate that. But because I don't know anything about Swift I just wanted to make sure you were trying to do the right thing. Commented Apr 22, 2016 at 2:50
  • Why would .cStringUsingEncoding(NSUTF8StringEncoding) return int8array? I'm confused how the strings and the int8 thing relate? Commented Apr 22, 2016 at 2:53

2 Answers 2

3

That's not perfect but at least it works.

let options = ["key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4"]

var keys = [String]()

for (key, value) in options {

    keys.append(key)
}

//you need to identify how many paramenters should be provided and set them following "static way"
//I did not find how to prepare this dynamically
let cKey1 = keys[0].cStringUsingEncoding(String.defaultCStringEncoding())!
let key1Pointer = UnsafePointer<CChar>(cKey1)

let cKey2 = keys[1].cStringUsingEncoding(String.defaultCStringEncoding())!
let key2Pointer = UnsafePointer<CChar>(cKey2)

let cKey3 = keys[2].cStringUsingEncoding(String.defaultCStringEncoding())!
let key3Pointer = UnsafePointer<CChar>(cKey3)

let cKey4 = keys[3].cStringUsingEncoding(String.defaultCStringEncoding())!
let key4Pointer = UnsafePointer<CChar>(cKey4)


let keysCArray = [key1Pointer, key2Pointer, key3Pointer, key4Pointer]

f(keysCArray)

/*
 C - code

 void f(const char * const *keywords) {

 printf("%s\n", keywords[0]);
 printf("%s\n", keywords[1]);
 printf("%s\n", keywords[2]);
 printf("%s\n", keywords[3]);

 }
 */

Hope it helps. I can share my sample app if you want.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks Melifaro. I had already tried this approach before posting the question, but kept getting EXC_BAD_ACCESS. So I thought the approach was wrong. But your point about identifying how many parameters made me realize the arrays need to be null terminated. So I added an UnsafePointer<Int8>(nil) to each array and it all works now. Thanks for turning on my lightbulb!
Nice finding! Thanks for feedback.
1

Here's an extension to Array in Swift3 that will do it generically:

public extension Array {

    // Translate [String] to (const char * const *), which translates to Swift as
    public func cStringArray() throws -> ArrayBridge<Element,CChar> {
        return try ArrayBridge<Element,CChar>(array:self) {
            guard let item = $0 as? String,
                  let translated = item.cString(using: .utf8) else {
                fatalError()
            }
            return translated
        }
    }
}

/*
 We need to have this intermediate object around to hold on to the translated objects, otherwise they will go away.
 The UnsafePointer won't hold on to the objects that it's pointing to.
 */
public struct ArrayBridge<SwiftType,CType> {

    let originals  :[SwiftType]
    let translated :[[CType]]
    let pointers   :[UnsafePointer<CType>?]
    public let pointer    :UnsafePointer<UnsafePointer<CType>?>

    init(array :[SwiftType], transform: @noescape (SwiftType) throws -> [CType]) throws {
        self.originals = array
        self.translated = try array.map(transform)

        var pointers = [UnsafePointer<CType>?]()
        for item in translated {
            pointers.append(UnsafePointer<CType>(item))
        }
        pointers.append(nil)
        self.pointers = pointers
        self.pointer = UnsafePointer(self.pointers)
    }
}

Then you can get the proper C-compatible pointer by doing

try stringArray.cStringArray().pointer

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.