快速桥接2D数组以键入UnsafePointer <unsafemutablepointer <double>?>?

时间:2018-12-20 01:29:56

标签: swift pointers pointer-to-pointer c-api

嗨,我正在尝试使用Swift 4包装一个C api

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

public typealias indicator = @convention(c) (Int32, UnsafePointer<UnsafePointer<Double>?>?, UnsafePointer<Double>?, UnsafePointer<UnsafeMutablePointer<Double>?>?) -> 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<UnsafeMutablePointer<Double>>指针?

根据这里的文档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);

2 个答案:

答案 0 :(得分:2)

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

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

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

虽然很容易从[T]UnsafePointer<T>进行桥接,但转到笨重的UnsafePointer<UnsafePointer<T>>UnsafePointer<UnsafeMutablePointer<T>>并不是那么容易。我也找不到与这些转换有关的任何文档。

我确实找到了一篇非常棒的博客文章,其中涉及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<Double>]的变量,然后可以将其传递给封闭体。

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<Double>]参数传递到body闭包将隐式桥接到导入的C API所需的UnsafePointer<UnsafePointer<Double>>和所需的UnsafePointer<UnsafeMutablePointer<Double>>

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

类型的其他人

答案 1 :(得分:0)

假设您在注释中声明了一个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)
        }
    }