How to get an array from a C function in Swift?

Viewed 802

I am working with a C function in my Swift code that outputs an array. The function doesn't return an array because, apparently in C, functions are discouraged from returning arrays. Therefore, the function takes an in-out parameter (as a pointer) and places the array there.

The C function:

void kRing(H3Index origin, int k, H3Index* out);

H3Index* is the out parameter that takes the array. However, how do I get the array from this function in Swift? H3Index*, the out parameter, points to an integer. And, apparently in C, you can point to an integer, pass that pointer to a function, and that function can place an array in that pointer's place (even though it's pointing to an integer).

But because of Swift's type safety, this makes it difficult to get the array from the function. The Swift version:

kRing(origin: H3Index, k: Int32, out: UnsafeMutablePointer<H3Index>!)

My Swift implementation:

let h3Index: H3Index = 600022775385554943 // integer
let k: Int32 = 2 // integer
var result = H3Index() // the in-out parameter (must be integer to satisfy Swift's type safety)
_ = withUnsafeMutablePointer(to: &result) { kRing(h3Index, k, $0) }
print(result)

And it prints the result (with a bad access error, which I don't care about right now). But the result is an integer when it should be an array. How is this done?

The C implementation, for reference:

H3Index indexed = 0x8a2a1072b59ffffL; // 64-integer (hex)
int k = 2; // integer
int maxNeighboring = maxKringSize(k); // integer

H3Index* neighboring = calloc(maxNeighboring, sizeof(H3Index)); // the out parameter (a pointer to an integer and/or array)

kRing(indexed, k, neighboring); // the function

for (int i = 0; i < maxNeighboring; i++) {
    if (neighboring[i] != 0) {
        // iterate through array
    }
}
1 Answers

In C,

H3Index* neighboring = calloc(maxNeighboring, sizeof(H3Index));
kRing(indexed, k, neighboring);

allocates memory for maxNeighboring elements of type H3Index and initializes the memory to zero. The address of that memory block (which is the address of the first element) is then passed to the kRing function.

It is possible to call calloc and free from Swift, but the easier to use API is Unsafe(Mutable)(Buffer)Pointer with its allocate() and deallocate() methods:

let neighboring = UnsafeMutableBufferPointer<H3Index>.allocate(capacity: maxNeighboring)
neighboring.initialize(repeating: 0)
kRing(indexed, k, neighboring.baseAddress)

Now you can print the values with

for i in 0..<maxNeighboring { print(neighboring[i]) }

or justs (because Unsafe(Mutable)BufferPointer is a collection that can be iterated over):

for neighbor in neighboring { print(neighbor) }

Eventually you must release the memory to avoid a memory leak:

neighboring.deallocate()

A simpler solution is to define a Swift array, and pass the address of the element storage to the C function:

var neighboring = Array<H3Index>(repeating: 0, count: maxNeighboring)
kRing(indexed, k, &neighboring)

for neighbor in neighboring { print(neighbor) }

neighboring is a local variable now, so that the memory is automatically released when the variable goes out of scope.

Related