快速桥接 2D 数组以键入 不安全指针<不安全可变指针?<Double>>?



嗨,我正在尝试使用 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>>指针?

根据这里的文档 使用指针参数调用函数 我应该能够通过隐式桥接来做到这一点,但似乎不是,也许我应该只创建指针类型而不是尝试从 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);

这里的问题在于 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数组指针的优秀博客文章,这让我大部分时间都在那里,博客是将字符串数组从 Swift 传递到 C。

在这里,他创建了一个指向[String]类型的UnsafeMutableBufferPointer指针,然后重新绑定内存,如下所示,然后继续使用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)
}
}

由于根据文档,转义桥接指针是未定义的行为,因此有必要在闭包中进行桥接和调用,正如 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]]

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

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

最新更新