如何将 Swift [Data] 转换为 char **

How to convert Swift [Data] to char **

我目前正在尝试将我的 Java Android 库移植到 Swift。在我的 Android 库中,我使用 Jerasure 的 JNI 包装器来调用以下 C 方法

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)

我不得不承认我对 Swift 比较陌生,所以我的一些东西可能是错误的。在我的 Java 代码中 char **data_ptrschar **coding_ptrs 实际上是二维数组(例如 byte[][] dataShard = new byte[3][1400])。这些二维数组包含实际的视频流数据。在我的 Swift 库中,我将视频流数据存储在 [Data] 数组中,因此问题是将 [Data] 数组转换为 C char ** 类型的正确方法是什么。

我已经尝试了一些方法,但 none 成功了。目前我有以下转换逻辑,它给了我一个 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
}

预期的结果是 jerasure 能够在调用 jerasure_matrix_decode 方法时恢复我的 [Data] 数组丢失的数据碎片,但它完全弄乱了我的 [Data] 数组并访问它导致EXC_BAD_ACCESS。所以我认为这是完全错误的方式。

jerasure.h 头文件中的文档写了以下关于 data_ptrs

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

编辑:

jerasure 库正在这样定义 data_ptrs

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

那么从 swift 调用 jerasure_matrix_decode 方法的最佳选择是什么?我应该使用不同于 [Data] 的东西吗?

可能的类似问题:

一个可能的解决方案是分配适当的内存并用数据填充它。

对齐方式

C 代码的 char ** 等价于 Swift 一侧的 UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>

在问题中显示的 data_ptrs 的定义中,我们看到每个数据块都将使用 malloc 分配。

C malloc的一个属性就是不知道最终会被强制转换成哪种指针​​类型。因此,它保证最严格的内存对齐:

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

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

特别是performance-critical C例程通常不会逐字节操作,而是转换为更大的数值类型或使用SIMD.

因此,根据您的内部 C 库实现,使用 UnsafeMutablePointer<CChar>.allocate(capacity: columns) 进行分配可能会有问题,因为

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

替代方法是使用带有对齐参数的 UnsafeMutableRawPointer。您可以使用 MemoryLayout<max_align_t>.alignment 找出最大对齐约束。

填充数据

UnsafeMutablePointer<CChar> 的优点是我们可以使用指针运算。这可以通过将 UnsafeMutableRawPointer 转换为 OpaquePointer 然后再转换为 UnsafeMutablePointer 来实现。在代码中它看起来像这样:

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

完成Self-contained测试程序

您的图书馆可能对数据有某些要求(例如支持的矩阵维度),我不知道。当然,这些都必须考虑在内。但对于基本的技术测试,我们可以创建一个独立的测试程序。

#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");
}

它只是打印字节并向每个字节添加 100,以便能够验证更改是否到达 Swift 端。

相应的 header 必须包含在桥 header 中,如下所示:

#ifndef matrix_h
#define matrix_h

void some_matrix_operation(int rows, int cols, char **data_ptrs);

#endif /* matrix_h */

在Swift这边,我们可以把所有东西都放在一个叫做class的矩阵中:

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

你可以这样调用它:

let example: [[UInt8]] = [
    [ 126, 127, 128, 129],
    [ 130, 131, 132, 133],
    [ 134, 135, 136, 137]
]
let dataArray = example.map { Data([=14=]) }

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)
    

测试结果

测试结果如下,似乎是预期的结果

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