首页 > 解决方案 > 如何将 Swift [Data] 转换为 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 库中,我将视频流数据存储在一个数组中,所以问题是将数组转换为 C类型[Data]的正确方法是什么。[Data]char **

我已经尝试了一些东西,但没有一个奏效。目前我有以下转换逻辑,它给了我一个UnsafeMutablePointer<UnsafeMutablePointer?>? 指针(数据 = [数据]

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 = 指向数据的 k 指针数组,大小为字节

编辑:

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

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

可能的类似问题:

  1. 如何创建 UnsafeMutablePointer<UnsafeMutablePointer<UnsafeMutablePointer<Int8>>>

标签: ioscswiftinteropswift5

解决方案


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

结盟

C 代码的等价物char **UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>在 Swift 端。

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

C malloc 的一个特性是它不知道最终将转换为哪种指针类型。因此,它保证了最严格的内存对齐:

如果分配成功,则返回的指针经过适当对齐,以便可以将其分配给具有基本对齐要求的任何类型对象的指针,然后用于访问分配的空间中的此类对象或此类对象的数组(直到空间被显式释放)。

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

特别是性能关键的 C 例程通常不会逐字节操作,而是转换为更大的数字类型或使用SIMD

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

UnsafeMutablePointer 不提供自动内存管理或对齐保证。见https://developer.apple.com/documentation/swift/unsafemutablepointer

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

填充数据

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

完整的独立测试程序

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

#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 端。

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

#ifndef matrix_h
#define matrix_h

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

#endif /* matrix_h */

在 Swift 方面,我们可以将所有内容放在一个名为 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
    }
    
}

您可以如下所示调用它:

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)
    

测试结果

测试结果如下,似乎显示了预期的结果。

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 
    

推荐阅读