0

I need to call C functions that take 2d arrays. The future arrays are created in Swift, and are generally [String], but I'm interested in the [[T]] case too.

The API I'm calling specifies that its functions always copy data passed to them if necessary, so I don't want create a copy of the contents myself just to pass it in, and I only need to worry about the pointers being valid until I can pass them into the C functions. My current approach is to do

    let addressArray = someArray.map { (array) in
        array.withUnsafeBufferPointer({ (buffer) in
            // get an array of addresses to each individual member
            return buffer.baseAddress
        })
    }

addressArray.withUnsafeBufferPointer { bufferPointer in
     someCFunction(bufferPointer.baseAddress)
     // do something with someArray to attempt to ensure that the pointers are valid past the lifetime of the withUnsafeBufferPointer call
}

This works, but as the pointers returned from withUnsafeBufferPointer are said to only be valid in that scope, it's clearly wrong (though by being certain to always require that someArray remain alive until after I'm done with someCFunction I feel fairly confident that it won't break in the short term). But there must be a better way; what is it?

Ben Pious
  • 4,765
  • 2
  • 22
  • 34
  • I don’t understand why you wouldn’t pass the Swift array. Does that fail? – matt Aug 18 '18 at 22:08
  • A C array is something like `char *`, a 2d version is `char **`. Such types are not bridged to Swift as Swift arrays, therefore it wouldn't type check. They are bridged as UnsafePointer. – Ben Pious Aug 18 '18 at 23:44
  • 1
    Well _some_ of that is not true. You can pass a Swift Array where a C array is expected. And although you certainly _describe_ a C 2-dimensional array as `char**`, it is _really_ just a one-dimensional buffer. So it seems to me you're just making extra work for yourself, unnecessarily. – matt Aug 19 '18 at 01:47
  • 1
    It's unclear here what the signature of `someCFunction` is. `String` is not always layout-equivalent to `char*`, so you can't just pass `[String]` where `char**` is expected in any case (certainly not in a way that doesn't force copying under the covers, even if it is working). As @matt notes, `char**` is not a "2d version" of an array. It's not even an array. It's a pointer to a pointer to a char. This isn't solvable in the general `[[T]]` case because `T` doesn't have a promised layout (it may not even be stored on the heap). – Rob Napier Aug 19 '18 at 03:18
  • 2
    For a specific case this is generally solvable (though Swift does not promise that it won't require copying; it may absolutely require copying). You're correct that the above code is incorrect and dangerous. What the correct code is depends on what the actual signature of `someCFunction` is. When you say "takes 2d arrays" do you mean it takes a matrix (i.e. every row has the same number of columns), or do you mean it takes an array of pointers? Are they `const` or non-`const`? – Rob Napier Aug 19 '18 at 03:22
  • For arrays of C strings, compare https://stackoverflow.com/questions/29469158/how-to-pass-an-array-of-swift-strings-to-a-c-function-taking-a-char-parameter – Martin R Aug 19 '18 at 04:29
  • @RobNapier I'm fine with Swift doing copies if necessary. I just don't want to do extra, potentially unnecessary ones in my own code. I mean pointer to pointers (char **), not a flat buffer. The API I'm calling doesn't care about the lifetime of the data in that pointer (as it either won't be stored or will be deep copied), so I don't think const or non const, or heap vs. stack storage matters in this case. The API also receives a count of the number of pointers in the first pointer, so the call really looks something like `someCFunction(pointer, count)`. – Ben Pious Aug 19 '18 at 17:15

2 Answers2

1

As you have experienced, your code would seemingly work occasionally. But as you have written, your code is wrong as the returned pointers from withUnsafeBufferPointer is valid only in the scope of its closure parameter.

And I do not understand what you mean it won't break in the short term, but I recommend you never to be confident about undocumented and unclear behaviors.

You may need to create some stable pointers, you can achieve that by allocating the regions explicitly.

I would write something like this when I need UnsafePointer<UnsafePointer<T>> to pass the content of nested Array [[T]]. (Assuming T is a primitive type.)

let bufPtrArray = someArray.map {subArray -> UnsafeMutableBufferPointer<T> in
    let bufPtr = UnsafeMutableBufferPointer<T>.allocate(capacity: subArray.count)
    _ = bufPtr.initialize(from: subArray)
    return bufPtr
}
defer {bufPtrArray.forEach{bufPtr in bufPtr.deallocate()}}

let addressArray = bufPtrArray.map {bufPtr in UnsafePointer(bufPtr.baseAddress!)}

addressArray.withUnsafeBufferPointer { bufferPointer in
    someCFunction(bufferPointer.baseAddress!)
    //...
}

Or, if you do not need codes specified in //..., you can just write:

someCFunction(addressArray)
OOPer
  • 47,149
  • 6
  • 107
  • 142
  • As I said in my question, I would like to do this *without* unnecessary allocations. Many of the types involved in these operations already have allocated contiguous memory that is in the right format, so it is wasteful to allocate copies. I'm looking for an API that ensures that those original buffers aren't deallocated until I'm done with them. – Ben Pious Aug 18 '18 at 23:58
  • Then you have no ways. Currently Swift does not provide any APIs you expect. – OOPer Aug 19 '18 at 00:02
  • 1
    Or, you can use `UnsafeMutableBufferPointer` instead of `[T]`. – OOPer Aug 19 '18 at 01:27
-1

The problem for me is that all the premises of the question seem false. Swift won't copy anything it doesn't have to, so there isn't necessarily any inefficiency in passing actual values. You can pass a Swift array where a C array is expected. And there isn't really any such thing as a multidimensional C array, so you can just pass a one-dimensional array (suitably arranged, of course).

For example, here's a method that takes an array of int along with information about how big a "row" (r) and a "column" (c) is to be (code taken from https://www.geeksforgeeks.org/dynamically-allocate-2d-array-c/):

- (void) giveMeACArray: (int*) array r: (int) r c: (int) c {
    int i, j, count;
    int *arr[r];
    for (i=0; i<r; i++)
        arr[i] = (int *)malloc(c * sizeof(int));
    count = 0;
    for (i = 0; i <  r; i++)
        for (j = 0; j < c; j++)
            arr[i][j] = ++count;
    // let's test it
    for (i = 0; i <  r; i++)
        for (j = 0; j < c; j++)
            printf("%d ", arr[i][j]);
}

Let's say that's part of the implementation of an Objective-C class called Thing. Okay then, here we are in Swift:

let t = Thing()
var arr : [CInt] = [1,2,3,4,5,6,7,8,9,10,11,12]
t.giveMeACArray(&arr, r: 3, c: 4)

Nothing got copied and the right answer got printed. So I don't see what your buffer pointers are for.

matt
  • 515,959
  • 87
  • 875
  • 1,141