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:
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