Convert Swift 2D array to C 2D array

$ swift --version
Apple Swift version 4.0 (swiftlang-900.0.63.10 clang-900.0.36)
Target: x86_64-apple-macosx10.9

Swift is convenient because you can easily call functions in C language. You can also pass Swift's ʻArray `to a function that receives a pointer in C, so it's easy to pass an array.

This is achieved by the mechanism that ʻArray is implicitly cast to ʻUnsafePointer <T> in the function call part.

But when it comes to 2D arrays, it doesn't work. Double pointers are used when receiving a two-dimensional array in C. For example in C

void f0(const int * const * p);

From Swift's point of view, the function defined as

func f0(_ p: UnsafePointer<UnsafePointer<CInt>?>!)

Is interpreted as. On the other hand, if there is let arr: Array <Array <CInt >> on the Swift side, the following will not work.

let arr: Array<Array<CInt>> = [ [0, 1, 2], [3, 4, 5] ]
f0(arr) //Compile error

Because, what you get by implicitly converting ʻArray <Array > `to a pointer is

UnsafePointer<Array<CInt>>

And

UnsafePointer<UnsafePointer<CInt>?>

Because it is not.

So, if you look at the transformation that takes ʻUnsafePointer from ʻArray <T>,

func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Array.Element>) throws -> R) rethrows -> R

Can be found. You may want to use it to write code like this: This ** compiles but shouldn't. ** **

let arr: Array<Array<CInt>> = [ [0, 1, 2], [3, 4, 5] ]
let bufPtrArr: Array<UnsafeBufferPointer<CInt>> = arr.map {
    return $0.withUnsafeBufferPointer { $0 }
}
let ptrArr: Array<UnsafePointer<CInt>?> = bufPtrArr.map { $0.baseAddress }
f0(ptrArr)

This is because the pointer passed to the closure of the withUnsafeBufferPointer method argument should only be used during the execution of this method.

It's a good use for recursive calls because you have to convert the arrays one by one to pointers, which can only be used during withUnsafeBufferPointer. After converting an array with withUnsafeBufferPointer, the procedure is to build an array of pointers by converting the next array inside the method.

This recursive process is hard to write many times, so let's make it an extension method of ʻArray for reuse, and get ʻArray <UnsafeBufferPointer <T >> from ʻArray <Array >I madewithUnsafeBufferPointerArray`.

extension Array {
    public func withUnsafeBufferPointerArray<T, R>(_ body: (Array<UnsafeBufferPointer<T>>) -> R) -> R
        where Element == Array<T>
    {
        var buffers = Array<UnsafeBufferPointer<T>>()
        var result: R?
        
        func recurse(body: (Array<UnsafeBufferPointer<T>>) -> R)
        {
            let i = buffers.count
            guard i < self.count else {
                result = body(buffers)
                return
            }
            self[i].withUnsafeBufferPointer { (buf: UnsafeBufferPointer<T>) in
                buffers.append(buf)
                recurse(body: body)
            }
        }
        
        recurse(body: body)
        
        return result!
    }
}

Using an in-function function makes it easy to write a process that combines this kind of state operation and recursive call. I don't like copying the return value as many times as there are elements, so I use ʻOptional and! `.

Now, with this you can call f0 as follows:

let arr: Array<Array<CInt>> = [ [0, 1, 2], [3, 4, 5] ]
arr.withUnsafeBufferPointerArray { (bufPtrArr: Array<UnsafeBufferPointer<CInt>>) in
    let ptrArr: Array<UnsafePointer<CInt>?> = bufPtrArr.map { $0.baseAddress }
    f0(ptrArr)
}

As a practical example, OpenGL ES

void glShaderSource(
	GLuint shader,
	GLsizei count,
	const GLchar * const *string,
	const GLint *length);

When used to call

    public static func shaderSource(_ id: GLuint,
                                    _ strings: Array<Array<GLchar>>)
    {
        strings.withUnsafeBufferPointerArray { (buffers: Array<UnsafeBufferPointer<GLchar>>) in
            glShaderSource(id,
                           GLsizei(buffers.count),
                           buffers.map { $0.baseAddress },
                           buffers.map { GLint($0.count) })
        }
    }

It's like that. It's nice that the count, string, and length arguments can all be easily constructed from buffers.

Recommended Posts

Convert Swift 2D array to C 2D array
Convert from C String pointer to Swift String type
[Java] Convert ArrayList to array
Pass C ++ char * to Swift
Convert alphabet to 26 base + array length
Convert 2D array to csv format with Java 8 Stream API
[Java] Convert array to ArrayList * Caution
Convert C language to JavaScript with Emscripten
Convert an array of strings to numbers
[Swift] Convert ByteArray and HexString to each other
Convert the array of errors.full_messages to characters and output
How to convert a file to a byte array in Java
Generate models from JSON to Swift, PHP, C #, JAVA
[Java] I want to convert a byte array to a hexadecimal number
Port C code with a lot of typecasts to Swift
Convert a Java byte array to a string in hexadecimal notation
Convert an array that may be null to a stream
[Ruby] How to batch convert strings in an array to numbers
Convert Java Powerpoint to XPS
Anything Array-11 [C # Refactoring Sample]
ABC --133- A & B & C & D
I want to convert characters ...
Convert String type to Timestamp type
Convert to Ruby Leet string
Convert Serializable Object to byte []
[Swift] How to use UserDefaults
ABC --122 --A & B & C & D
Convert from ○ months to ○ years ○ months
ABC --125- A & B & C & D
ABC --130- A & B & C & D
ABC --126 --A & B & C & D
How to initialize Java array
I want to convert an array to Active Record Relation with Rails
[Swift5] How to get an array and the complement of arrays