Search code examples
ioscswiftinteropswift5

How to convert Swift [Data] to char **


I'm currently trying to port my Java Android library to Swift. In my Android library I'm using a JNI wrapper for Jerasure to call following C method

int jerasure_matrix_decode(int k, int m, int w, int *matrix, int row_k_ones, int *erasures, char **data_ptrs, char **coding_ptrs, int size)

I have to admit that I'm relatively new to Swift so some of my stuff might be wrong. In my Java code char **data_ptrs and char **coding_ptrs are actually two dimensional arrays (e.g. byte[][] dataShard = new byte[3][1400]). These two dimensional arrays contain actual video stream data. In my Swift library I store my video stream data in a [Data] array so the question is what is the correct way to convert the [Data] array to the C char ** type.

I already tried some things but none of them worked. Currently I have following conversion logic which gives me a UnsafeMutablePointer<UnsafeMutablePointer?>? pointer (data = [Data])

let ptr1 = ptrFromAddress(p: &data)
ptr1.withMemoryRebound(to: UnsafeMutablePointer<Int8>?.self, capacity: data.count) { pp in
    // here pp is UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?
}

func ptrFromAddress<T>(p:UnsafeMutablePointer<T>) -> UnsafeMutablePointer<T>
{
   return p
}

The expected result would be that jerasure is able to restore missing data shards of my [Data] array when calling the jerasure_matrix_decode method but instead it completely messes up my [Data] array and accessing it results in EXC_BAD_ACCESS. So I expect this is completely the wrong way.

Documentation in the jerasure.h header file writes following about data_ptrs

data_ptrs = An array of k pointers to data which is size bytes

Edit:

The jerasure library is defining the data_ptrs like this

#define talloc(type, num) (type *) malloc(sizeof(type)*(num))

char **data;

data = talloc(char *, k);
for (i = 0; i < k; i++) {
  data[i] = talloc(char, sizeof(long)*w);
}

So what is the best option to call the jerasure_matrix_decode method from swift? Should I use something different than [Data]?

Possible similar question:

  1. How to create a UnsafeMutablePointer<UnsafeMutablePointer<UnsafeMutablePointer<Int8>>>

Solution

  • A possible solution could be to allocate appropriate memory and fill it with the data.

    Alignment

    The equivalent to char ** of the C code would be UnsafeMutablePointer<UnsafeMutablePointer<CChar>?> on Swift side.

    In the definition of data_ptrs that you show in your question, we see that each data block is to be allocated with malloc.

    A property of C malloc is that it does not know which pointer type it will eventually be cast into. Therefore, it guarantees strictest memory alignment:

    The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement and then used to access such an object or an array of such objects in the space allocated (until the space is explicitly deallocated).

    see https://port70.net/~nsz/c/c11/n1570.html#7.22.3

    Particularly performance-critical C routines often do not operate byte by byte, but cast to larger numeric types or use SIMD.

    So, depending on your internal C library implementation, allocating with UnsafeMutablePointer<CChar>.allocate(capacity: columns) could be problematic, because

    UnsafeMutablePointer provides no automated memory management or alignment guarantees. see https://developer.apple.com/documentation/swift/unsafemutablepointer

    The alternative could be to use UnsafeMutableRawPointer with an alignment parameter. You can use MemoryLayout<max_align_t>.alignment to find out the maximum alignment constraint.

    Populating Data

    An UnsafeMutablePointer<CChar> would have the advantage that we could use pointer arithmetic. This can be achieved by converting the UnsafeMutableRawPointer to an OpaquePointer and then to an UnsafeMutablePointer. In the code it would then look like this:

    let colDataRaw = UnsafeMutableRawPointer.allocate(byteCount: cols, alignment: MemoryLayout<max_align_t>.alignment)
    let colData = UnsafeMutablePointer<CChar>(OpaquePointer(colDataRaw))
    for x in 0..<cols {
        colData[x] = CChar(bitPattern: dataArray[y][x])
    }
    

    Complete Self-contained Test Program

    Your library will probably have certain requirements for the data (e.g. supported matrix dimensions), which I don't know. These must be taken into account, of course. But for a basic technical test we can create an independent test program.

    #include <stdio.h>
    #include "matrix.h"
    
    void some_matrix_operation(int rows, int cols, char **data_ptrs) {
        printf("C side:\n");
        for(int y = 0; y < rows; y++) {
            for(int x = 0; x < cols; x++) {
                printf("%02d ", (unsigned char)data_ptrs[y][x]);
                data_ptrs[y][x] += 100;
            }
            printf("\n");
        }
        printf("\n");
    }
    

    It simply prints the bytes and adds 100 to each byte to be able to verify that the changes arrive on the Swift side.

    The corresponding header must be included in the bridge header and looks like this:

    #ifndef matrix_h
    #define matrix_h
    
    void some_matrix_operation(int rows, int cols, char **data_ptrs);
    
    #endif /* matrix_h */
    

    On the Swift side, we can put everything in a class called Matrix:

    import Foundation
    
    class Matrix: CustomStringConvertible {
        let rows: Int
        let cols: Int
        let dataPtr: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>
    
        init(dataArray: [Data]) {
            guard !dataArray.isEmpty && !dataArray[0].isEmpty else { fatalError("empty data not supported") }
            self.rows = dataArray.count
            self.cols = dataArray[0].count
            self.dataPtr = Self.copyToCMatrix(rows: rows, cols: cols, dataArray: dataArray)
        }
        
        deinit {
            for y in 0..<rows {
                dataPtr[y]?.deallocate()
            }
            dataPtr.deallocate()
        }
        
        var description: String {
            var desc = ""
            for data in dataArray {
                for byte in data {
                    desc += "\(byte) "
                }
                desc += "\n"
            }
            return desc
        }
        
        var dataArray: [Data]  {
            var array = [Data]()
            for y in 0..<rows {
                if let ptr = dataPtr[y] {
                    array.append(Data(bytes: ptr, count: cols))
                }
            }
            return array
        }
    
        
        private static func copyToCMatrix(rows: Int, cols: Int, dataArray: [Data]) -> UnsafeMutablePointer<UnsafeMutablePointer<CChar>?> {
            let dataPtr = UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>.allocate(capacity: rows)
            for y in 0..<rows {
                let colDataRaw = UnsafeMutableRawPointer.allocate(byteCount: cols, alignment: MemoryLayout<max_align_t>.alignment)
                let colData = UnsafeMutablePointer<CChar>(OpaquePointer(colDataRaw))
                dataPtr[y] = colData
                for x in 0..<cols {
                    colData[x] = CChar(bitPattern: dataArray[y][x])
                }
            }
            return dataPtr
        }
        
    }
    

    You can call it as shown here:

    let example: [[UInt8]] = [
        [ 126, 127, 128, 129],
        [ 130, 131, 132, 133],
        [ 134, 135, 136, 137]
    ]
    let dataArray = example.map { Data($0) }
    
    let matrix = Matrix(dataArray: dataArray)
    
    print("before on Swift side:")
    print(matrix)
    
    some_matrix_operation(Int32(matrix.rows), Int32(matrix.cols), matrix.dataPtr)
    
    print("afterwards on Swift side:")
    print(matrix)
        
    

    Test Result

    The test result is as follows and seems to show the expected result.

    before on Swift side:
    126 127 128 129 
    130 131 132 133 
    134 135 136 137 
    
    C side:
    126 127 128 129 
    130 131 132 133 
    134 135 136 137 
    
    afterwards on Swift side:
    226 227 228 229 
    230 231 232 233 
    234 235 236 237