1

To be clear I want to send my C function an array of null strings of fixed length like:

["\0\0\0\0\0\0", "\0\0\0\0\0\0", "\0\0\0\0\0\0"]

expect the array to return populated like:

["1.2345", "6.7891", "0.1112"]

[update ----------->]

I've seen in various places that people have had some success with copying the withArayOfCstrings() function from the SwiftPrivate code from the Swift source code repository. An explanation of why this works (at one time) is this highly cited source: https://oleb.net/blog/2016/10/swift-array-of-c-strings/ (tl;dr swift developers also needed a way to pass strings between Swift and C for QA.) However it seems this has stopped working as I've also seen no success from more recent asks. It seems 4 years ago this may have still worked. One post I ran into mentioned that they believed something changed in version 4?

The crux of the issue seems that a 2 dimensional array that used to be *'char * const ' in C used to be representable in Swift as [UnsafeMutablePointer?]. The multi-dimensional array now seems to be represented by a double pointer 'char ** ' in C and this now looks like UnsafeMutablePointer<UnsafeMutablePointer?> in Swift. This is where it seems everyone gets stuck and have no way of creating such an object to pass to be able to pass.

[ <----------- /update]

I've attached the GMP library to an Xcode Project and wanted to test out some functionality by sending an array of inputs, and read out an array of outputs. I just can't seem to figure out how to pass out an array of char* from C to Swift.

The error indicates I need a specific format. I've seen examples of just one char* but not an array of them and I can't fathom from that the correct way I construct a proper unsafe pointer for an array.

my C header

#define GMP_STR_MAXLEN 256
void get_gmp_calculations(const signed long int inputs_x[], const signed long int inputs_y[], int inputs_len, char *outputs[]);


my Swift code

func test_gmp(int_inputs_x: [Int], int_inputs_y: [Int])
{
    var outputs: [[CChar]] = [[CChar]](repeating: [CChar](repeating: 0, count: Int(GMP_STR_MAXLEN)), count: int_inputs_x.count)
    var unsafe_outputs: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>? = //??? something something "outputs"
    get_gmp_calculations(int_inputs_x, int_inputs_y, Int32(int_inputs_x.count), unsafe_outputs)
1

1 Answer 1

0

For the sake of simplicity, suppose the C function fills outputs with 2 strings, each with length 5.

void c_function(char *outputs[]) {
    outputs[0][0] = '1';
    outputs[0][1] = '2';
    outputs[0][2] = '3';
    outputs[0][3] = '4';
    outputs[0][4] = '5';
    outputs[1][0] = '6';
    outputs[1][1] = '7';
    outputs[1][2] = '8';
    outputs[1][3] = '9';
    outputs[1][4] = '0';
}

To call this in Swift, I would create a [UnsafeMutablePointer<CChar>]. Allocate 2 pointers, and initialise them to 0.

var arr = (0..<2).map { _ in
    let buffer = UnsafeMutablePointer<CChar>.allocate(capacity: 6) // 6, because strings are null-terminated
    buffer.initialize(repeating: 0, count: 6)
    return Optional(buffer)
}
defer {
    for elem in arr {
        elem?.deallocate()
    }
}

Then you can call c_function(&arr).

If you want the result as a [String] or [[CChar]], you can do:

let stringResult = arr.compactMap { $0.flatMap { String(cString: $0, encoding: .utf8) } } // remember to use the correct encoding!
// or
let cCharResult = arr.compactMap { Array(UnsafeMutableBufferPointer(start: $0, count: 6)) } // use count: 5 here if you don't need the terminating null char

Not the answer you're looking for? Browse other questions tagged or ask your own question.