Swift 桥接二维数组以键入 UnsafePointer<UnsafeMutablePointer<Double>?>?

Posted

技术标签:

【中文标题】Swift 桥接二维数组以键入 UnsafePointer<UnsafeMutablePointer<Double>?>?【英文标题】:Swift bridging 2D Array to type UnsafePointer<UnsafeMutablePointer<Double>?>? 【发布时间】:2019-05-20 13:02:24 【问题描述】:

您好,我正在尝试使用 Swift 4 封装 C api

Swift 已导入具有以下签名的函数。

public typealias indicator = @convention(c) (Int32, UnsafePointer&lt;UnsafePointer&lt;Double&gt;?&gt;?, UnsafePointer&lt;Double&gt;?, UnsafePointer&lt;UnsafeMutablePointer&lt;Double&gt;?&gt;?) -&gt; Int32

根据 C 库文档,签名如下:

int indicator(int size, double const *const *inputs, double const *options, double *const *outputs);

值得注意的是,函数返回的int是c风格的函数的错误类型,实际返回在outputs指针中

那么假设我创建了以下 Swift 类型

let inputs: [[Double]] = [] let options: [Double] = [] var outputs: [[Double]] = []

使用一些适当的值,我应该能够执行以下操作:(注意 info.pointee.indicator 是导入的函数)

internal func calculateIndicator(options opts: [Double], input inputs: [[Double]], output outPuts: inout [[Double]]) -> [[Double]]? 
    guard let sz = inputs.first?.count else fatalError("Must supply a [[Double]] input param")

    let inputPointer = UnsafePointer<[Double]>(inputs)
    let optionsPointer = UnsafePointer<Double>(opts)
    var outputPointer = UnsafeMutablePointer<[Double]>(&outPuts)

    let val = info.pointee.indicator(Int32(sz), inputPointer, optionsPointer, outputPointer)

    // do something with the outputs and return the values

但是编译器报错如下:

Cannot invoke 'indicator' with an argument list of type '(Int32, UnsafePointer<[Double]>, UnsafePointer<Double>, UnsafeMutablePointer<[Double]>)'

这很有意义,因为我传递了不正确的类型(我认为)。

那么,除了内存管理问题,我将如何将[[Double]] 类型转换为例如UnsafePointer&lt;UnsafeMutablePointer&lt;Double&gt;&gt; 指针?

根据Calling Functions With Pointer Parameters 此处的文档,我应该能够通过隐式桥接来做到这一点,但似乎不行,也许我应该只创建指针类型而不是尝试从 Swift 转换?

提前致谢,我确定我遗漏了一些简单的东西。

C API 本身如下:

typedef int (*indicator_function)(int size,
  double const *const *inputs,
  double const *options,
  double *const *outputs);

typedef struct indicator_info 
  char *name;
  char *full_name;
  indicator_start_function start;
  indicator_function indicator;
  int type, inputs, options, outputs;
  char *input_names[MAXINDPARAMS];
  char *option_names[MAXINDPARAMS];
  char *output_names[MAXINDPARAMS];
 indicator_info;

indicator 函数通过上面的结构访问。

指标函数的给定实例如下

int add(int size,
  TI_REAL const *const *inputs,
  TI_REAL const *options,
  TI_REAL *const *outputs);

【问题讨论】:

“Swift 已导入具有以下签名的函数” 不。据此,indicator 不是您调用的函数。它是您传递的函数的类型(签名)。显示我们实际必须传递它的 C API。 嗯,它是一大堆函数的类型别名,它们都具有以下签名public func indicator_abs(_ size: Int32, _ inputs: UnsafePointer&lt;UnsafePointer&lt;Double&gt;?&gt;!, _ options: UnsafePointer&lt;Double&gt;!, _ outputs: UnsafePointer&lt;UnsafeMutablePointer&lt;Double&gt;?&gt;!) -&gt; Int32。我已经编辑了问题。 【参考方案1】:

这里的问题在于 C API 需要这些参数 double *const *outputsdouble const *const *inputs 或 Swift 术语中的 [[Double]] 类型。

这个C函数签名被Swift分别导入以下类型。

UnsafePointer<UnsafeMutablePointer<Double>?> 
UnsafePointer<UnsafePointer<Double>?>

虽然从[T]UnsafePointer&lt;T&gt; 很容易过渡到庞大的UnsafePointer&lt;UnsafePointer&lt;T&gt;&gt;UnsafePointer&lt;UnsafeMutablePointer&lt;T&gt;&gt; 却不是那么容易。我也找不到与这些转换相关的任何文档。

我确实找到了 Ole Begemann 撰写的关于指向 UInt8 数组的指针的优秀博客文章,这让我大部分时间到达那里,博客是 Passing an Array of Strings from Swift to C。

在此他创建了一个UnsafeMutableBufferPointer 指向[String] 类型的指针,然后重新绑定内存,如下所示,然后继续使用CChar 数组的偏移量,您可以在上面引用的文章

public func withArrayOfCStrings<R>(
   _ args: [String],
   _ body: ([UnsafeMutablePointer<CChar>?]) -> R) -> R 
      let argsCounts = Array(args.map  $0.utf8.count + 1 )
      let argsOffsets = [ 0 ] + scan(argsCounts, 0, +)
      let argsBufferSize = argsOffsets.last!

      var argsBuffer: [UInt8] = []
      argsBuffer.reserveCapacity(argsBufferSize)
      for arg in args 
          argsBuffer.append(contentsOf: arg.utf8)
          argsBuffer.append(0)
      

     return argsBuffer.withUnsafeMutableBufferPointer  (argsBuffer) in
         let ptr = UnsafeMutableRawPointer(argsBuffer.baseAddress!).bindMemory(
         to: CChar.self, capacity: argsBuffer.count)
         var cStrings: [UnsafeMutablePointer<CChar>?] = argsOffsets.map  ptr + $0 
         cStrings[cStrings.count - 1] = nil
         return body(cStrings)
     

由于根据docs 转义桥接指针是未定义的行为,因此有必要按照 Ole Bergmann 在他的文章中建议的那样在闭包内进行桥接和调用。

为了实现这一点,我们创建了一个类似的函数:

func indicatorWithArrays<R>(inputs ins:[[Double]],
                            options opts: [Double],
                            outputs out: [[Double]],
                            ti body: ([UnsafePointer<Double>?], 
                                      UnsafePointer<Double>?, 
                                      [UnsafeMutablePointer<Double>?]) -> R) -> R

这是对R 类型的泛型,其返回类型和以前一样。

在函数内部,我们将输入和输出桥接到UnsafeBufferPointer,然后在结果缓冲区上调用map 以创建[UnsafePointer&lt;Double&gt;] 类型的变量,然后可以将其传递给闭包主体。

return ins.withUnsafeBufferPointer  (inputsBuffer) in
    var inPuts: [UnsafePointer<Double>?] = inputsBuffer.map  UnsafePointer($0)                                       
    return out.withUnsafeBufferPointer  (outputsBuffer) in
            var outPtrPtr: [UnsafeMutablePointer<Double>?] 
                = outputBuffer.map  UnsafeMutablePointer(mutating: $0)                  
            return body(inPuts, opts, outPtrPtr)
        
    

[UnsafePointer&lt;Double&gt;] 参数传递给body 闭包隐式桥接到所需的UnsafePointer&lt;UnsafePointer&lt;Double&gt;&gt; 和导入的C API 所需的UnsafePointer&lt;UnsafeMutablePointer&lt;Double&gt;&gt;

indicatorWithArrays 函数调用如下,然后允许我们在导入的 C 函数中使用桥接指针:

return indicatorWithArrays(inputs: input, options: opts, outputs: resArray)  (input, opts, outputs) in
            let sz = inputs?.first?.count ?? 0
            switch TIReturnType(rawValue: tulipInfo.info.pointee.indicator(Int32(sz), input, opts, outputs)) 
            case .ti_okay?:
                for (index, item) in outputs.enumerated() 
                    let buff = UnsafeBufferPointer(start: item, count: resArray[index].count)
                    resArray[index] = Array(buff)
                
                return resArray
            case nil:
                return nil
            
        

电话在哪里:tulipInfo.info.pointee.indicator(Int32(sz), input, opts, outputs)

Magic 是关于函数之间的传递闭包,从而确保我们不会逃避桥接指针,Ole Bergmann 的解决方案适用于 String 类型,但希望这对其他人有所帮助谁被 [[T]] 类型卡住了

【讨论】:

【参考方案2】:

假设你有一个 C 函数,正如你在评论中所说的那样,Swift 键入了这样的 C 函数:

public func indicator_abs(_ size: Int32, 
    _ inputs: UnsafePointer<UnsafePointer<Double>?>!, 
    _ options: UnsafePointer<Double>!, 
    _ outputs: UnsafePointer<UnsafeMutablePointer<Double>?>!) -> Int32

...那我想你可以这样称呼它:

    let inputs = [1.0, 2.0]
    let options = [1.0, 1.0]
    var outputs = [0.0, 0.0]

    let result:Int32 = withUnsafePointer(to: inputs)  inputsPtr in
        withUnsafePointer(to: &outputs)  outputsPtr in
            indicator_abs(2,inputsPtr,options,outputsPtr)
        
    

【讨论】:

谢谢,但有几件事,参数inputsoutputs 的类型为[[Double]],并且在C 风格中result 是实际输出的函数的错误类型在outputs 中需要。所以我认为这行不通,因为我认为我们无法逃脱outputsPtr

以上是关于Swift 桥接二维数组以键入 UnsafePointer<UnsafeMutablePointer<Double>?>?的主要内容,如果未能解决你的问题,请参考以下文章

iOS Swift:在自定义 UITableView 单元格中更新二维数组时获取重复值

Swift:“数据”类型的值错误以键入“数据”

swift 如何在swift中制作一个二维数组

Swift:填充二维数组时出现索引超出范围错误

Swift 问题 - 数组列表中包含二维数组的字典

在 Swift 中将一列值插入到二维数组中