如何将一个结构体数组传递给C API?

3

我有一个看起来像这样的C API:

typedef struct Datapoints {
  double *ptr;
  uintptr_t len;
} Datapoints;

double my_function(const struct Datapoints *baseline_data,
                     uint32_t baseline_data_len,
                     const struct Datapoints *new_data);

我发现从Swift调用此函数很困难。第一个参数是指向Datapoints结构体的指针数组,第三个参数是单个Datapoints结构体。

这是我想到的方法,但由于多种原因它无法工作,主要原因是我试图存储一系列不安全的指针以便稍后将它们传递给my_function。我已经通过重命名变量和类型来混淆代码,如果它不能编译,请原谅我。代码基本上看起来像这样:

var baseline_datapoints = [Datapoints]()
var current_datapoints:Datapoints = Datapoints()

// This loop was intended to build up an array `baseline_Datapoints` containing pointers to struct `Datapoints`
for reading in baseline {
    let datapoints:[Double] = reading.datapoints

    // Won't work: The pointer passed as an argument to `body` is valid only during the
    // execution of `withUnsafeMutableBufferPointer(_:)`. Do not store or return
    // the pointer for later use.
    // We _are_ attempting to store it for later use. 

    datapoints.withUnsafeMutableBufferPointer{ ptr in
        baseline_datapoints.append(Datapoints(ptr: ptr.baseAddress, len: UInt(datapoints.count)) )
    }
}

self.newData.withUnsafeMutableBufferPointer {ptr in
    current_datapoints = Datapoints(ptr: ptr.baseAddress, len: UInt(self.newData.count))
}

let x = baseline_datapoints.withUnsafeBufferPointer{
    (baselineptr: UnsafeBufferPointer<Datapoints>) -> Double in
    return withUnsafePointer(to: current_datapoints) {
        (current_ptr: UnsafePointer<Datapoints>) -> Double in

        return my_function(baseline_ptr.baseAddress, UInt32(baseline_datapoints.count ), current_ptr)
    }
}

我认为我应该扩展withUnsafeXxxPointer闭包的范围,以便数据保持足够长的有效期。然而,在尝试构建baseline_data列表时如何做我并不确定。

此外,对于每个不安全指针使用一个闭包似乎很不方便。如果我那样做,它将导致代码嵌套深度增加。有其他方法吗?

欢迎任何建议。

编辑:

接受的答案完美地解决了问题,只是第三个参数是按值传递的。 然而,这对于 C API 来说实际上是更好的设计,所以我将我的 API 更改为如下:

double my_function(const struct Datapoints *baseline_data,
                     uint32_t baseline_data_len,
                     const struct Datapoints new_data);

这里记录了输入的两个参数baseline_datanew_data


1
当您描述类似于C API的内容时,仅提及参数和返回类型是不够的。您还需要指定函数遵循的内存管理约定,这不能通过API来表达(通常在头文件文档中或使用CoreFoundarion中的“复制规则”和“获取规则”等命名约定进行描述)。如果没有这个,我们就无法知道从Swift传递值的正确方式。 - Alexander
根据我对提供的示例代码的阅读,看起来new_data是“输出”参数(它指向的double值将被修改),而baseline_data是“输入”。但这只是一种假设,因为问题中没有指定。 - jtbandes
是的,@Alexander 提出了很好的观点,它们实际上都是 _输入参数_。事实上,我已经改变了函数签名,使得 new_data 现在通过值传递。你说得对,这是不明确的。 - pnadeau
你还需要指定函数如何处理这些输入。它是立即使用还是稍后储存以供其他用途?这决定了临时指针是否足够,或者您是否需要做一些更特殊的事情。 - Alexander
1个回答

0

不幸的现实是,在Swift中,存储和重复使用ptr.baseAddress的值在传递给datapoints.withUnsafeMutableBufferPointer的闭包之外是不安全的。这已经被记录

…指针参数仅在方法执行期间有效。

这意味着在for循环中无法完成您尝试做的事情*,同样适用于您让baseAddressself.newData.withUnsafeMutableBufferPointer { }中逃逸的方式。

最清晰、最安全的解决方案需要实际上进行复制底层的Doubles。代码看起来会更像:

var baseline_bufs = [UnsafeMutableBufferPointer<Double>]()
defer {
  // don't forget to deallocate memory that was allocated!
  for d in baseline_bufs {
    d.deallocate()
  }
}

// Copy data from baseline readings into UnsafeMutableBufferPointers
for reading in baseline {
  d = UnsafeMutableBufferPointer<Double>.allocate(capacity: reading.datapoints.count)
  d.initialize(from: reading.datapoints)
  baseline_bufs.append(d)
}


let x = self.newData.withUnsafeMutableBufferPointer { newDataBuf in
  let new_ptr = Datapoints(ptr: newDataBuf.baseAddress, len: UInt(newDataBuf.count))

  // convert each `UnsafeMutableBufferPointer` to a `Datapoints`
  let baseline_datapoints = baseline_bufs.map { Datapoints(ptr: $0.baseAddress, len: UInt($0.count)) }
  return baseline_datapoints.withUnsafeBufferPointer {
    return my_function($0.baseAddress, UInt32($0.count), new_ptr)
  }
}

也许有一种方法可以使用递归将所有的withUnsafeX调用嵌套起来,但是根据数组中的项目数量,您可能会遇到堆栈大小限制。

是的,你要么从一开始就使用UnsafeBufferPointer,要么坚持使用Array并根据需要进行复制。前者更具性能(如果有必要),而后者更容易和更安全。 - Alexander
如果 OP 的原始数据来源是一个数组的数组,我认为除非代码可以重构为完全不使用数组,否则没有任何安全的避免复制的方法。(基本上,这就是 UnsafeMutableBufferPointer 设计的用途。)由于 API 接受一个指向包含 UnsafePointers 的结构体数组的 UnsafePointer,我选择为内部使用明确的 UnsafeMutableBufferPointers,并为外部使用 Array + .withUnsafeMutableBufferPointer。 - jtbandes
抄袭?我就知道会变成这样。非常感谢你详细的回答,我很快就会尝试并告诉你结果。 - pnadeau
是的,我可以确认这种方法可行。谢谢你的帮助。 - pnadeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接