C
Swift

Swiftの2次元配列をCの2次元配列に変換する

More than 1 year has passed since last update.
$ swift --version
Apple Swift version 4.0 (swiftlang-900.0.63.10 clang-900.0.36)
Target: x86_64-apple-macosx10.9

Swift は C言語の関数を簡単に呼び出せて便利です。 Cでポインタを受けている関数に、Swiftの Array<T> を渡したりもできるので、配列の受け渡しも簡単です。

これは関数呼び出し部分において、 Array<T>UnsafePointer<T> に暗黙キャストされるという仕組みで実現されています。

しかし、これが2次元配列となるとうまくいきません。 Cで2次元配列を受ける場合、ダブルポインタが使用されます。例えばCで

void f0(const int * const * p);

と定義されている関数は、 Swift から見ると、

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

と解釈されます。これに対して、 Swift 側に let arr: Array<Array<CInt>> があったとして、下記はうまく動きません。

let arr: Array<Array<CInt>> = [ [0, 1, 2], [3, 4, 5] ]
f0(arr) // コンパイルエラー

なぜなら、 Array<Array<CInt>> をポインタへ暗黙変換して得られるのは、

UnsafePointer<Array<CInt>>

であって、

UnsafePointer<UnsafePointer<CInt>?>

ではないからです。

そこで、 Array<T> から UnsafePointer<T> を取り出す変換を調べると、

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

が見つかります。これを使って以下のようなコードを書きたくなるかもしれません。これは、 コンパイルは通るけどやってはいけません。

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)

なぜなら、 withUnsafeBufferPointer メソッドの引数のクロージャに渡されるポインタは、このメソッドの実行中しか使用してはいけないからです。

配列を1つずつポインタに変換しなければならないのに、そのポインタは withUnsafeBufferPointer の期間中しか使えないので、再帰呼び出しの使い所です。
ある配列を withUnsafeBufferPointer で変換したら、そのメソッドの内部で、さらに次の配列を変換するようにして、ポインタの配列を構築していく手順です。

この再帰処理は何度も書くのはつらいので、再利用するために Array の拡張メソッドにするとして、 Array<Array<T>> から Array<UnsafeBufferPointer<T>> を得るメソッド withUnsafeBufferPointerArray を作りました。

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!
    }
}

関数内関数を使うとこの手の状態操作と再帰呼び出しを組み合わせた処理が書きやすくて良いですね。返り値のコピーが要素数の数だけ繰り返されるのが嫌なので Optional! を使っています。

さて、これを使うと以下のようにして f0 の呼び出しができます。

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)
}

実用例としては、 OpenGLESの

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

の呼び出しに使用すると

    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) })
        }
    }

という感じです。 count 引数、 string 引数、 length 引数が、どれも buffers から簡単に構築できていて良い感じですね。