首页 > 解决方案 > Swift中的字节数组指针范围

问题描述

我在需要指向uint8_t缓冲区的指针的库中调用 C 函数。我试图在 Swift 中引用 UInt8 数组,如下所示(稍后传递给 C 函数):

import Foundation

struct CStruct {
    let val: UnsafeMutablePointer<UInt8>
    let count: UInt32
}

var structs = [CStruct]()
var datas = [[UInt8]]()

for _ in 0..<10 {
    // In the real application, Data contains data rather than being zero length
    let d = Data()
    var data = [UInt8](d)
    // I don't want the `data` buffers to be deallocated, so I store them in an array.
    datas.append(data)
    structs.append(CStruct(val: &data, count: UInt32(data.count)))
}

print("Structs: \(structs)")
// pass the structs to a C function which processes them

这给出了警告

Inout 表达式创建一个临时指针,但参数 'val' 应该是一个比对 'init(val:count:)' 的调用更长的指针

我不能使用data.withUnsafeMutableBufferPointer它提供的指针,因为它超出了闭包的范围。我不能使用Unmanaged它,因为它只对类起作用,并且Array是一个结构。我也不确定将data数组添加到父作用域中的数组是否会延长其生命周期(因为它是一个结构)。实现这一目标的正确方法是什么?


编辑:

我的解决方案,基于@bscothern 的回答:

import Foundation

struct CStruct {
    let val: UnsafeMutablePointer<UInt8>
    let count: UInt32
}

var structs = [CStruct]()
var dataStorage: [UnsafeMutablePointer<UInt8>] = []

for _ in 0..<10 {
    // In the real application, Data contains data rather than being zero length
    let d = Data()
    let ptr = UnsafeMutablePointer<UInt8>.allocate(capacity: d.count)
    d.withUnsafeBytes { (buff) -> Void in
        ptr.initialize(from: buff.bindMemory(to: UInt8.self).baseAddress!, count: d.count)
    }
    dataStorage.append(ptr)
    structs.append(CStruct(val: ptr, count: UInt32(d.count)))
}
defer {
    for ptr in dataStorage {
        ptr.deallocate()
    }
    dataStorage = []
}

print("Structs: \(structs)")
// pass the structs to a C function which processes them

也许有更好的方法来复制Data

标签: cswift

解决方案


有很多方法可以解决这个问题,但根本原因是你需要分配一个缓冲区并将其传入。然后你需要有一种方法来知道你什么时候清理它,这样你就不会泄漏内存.

解决这个问题的一个相当简单的方法是使用类类型来管理CStruct实例的生命周期。这是一个使用类包装器使大部分内存管理自动化的方法示例。

import Foundation

struct CStruct {
    let val: UnsafeMutablePointer<UInt8>
    let count: UInt32
}

final class CStructLifetimeManager {
    private(set) var cStruct: CStruct // This needs to be mutable so it can be deallocated later on.

    private init(count: Int) {
        var val = UnsafeMutablePointer<UInt8>.allocate(capacity: count)
        for i in 0..<10 {
            val[I] = 0
        }
        self.cStruct = CStruct(val: val, count: UInt32(count))
    }

    deinit {
        cStruct.val.deinitialize(count: 10) // Not needed if you really just have UInt8 types since they are trivial
        cStruct.val.deallocate()
    }

    static func createStruct(count: Int) -> CStructLifetimeManager {
        CStructLifetimeManager(count: count)
    }
}

var structs = [CStruct]()
var structLifetimeManagers = [CStructLifetimeManager]() // This now keeps the manager objects alive

for _ in 0..<10 {
    let cStructLifetimeManager = CStructLifetimeManager(count: 10)
    structs.append(cStructLifetimeManager.cStruct)
    structLifetimeManagers.append(cStructLifetimeManager)
}

print("Structs: \(structs)")

这种方法的问题是structs镜像structLifetimeManagers相同的CStruct实例,但它们仍然不同。这意味着它可以停止存在于遗嘱中structLifetimeManagersstructs然后访问无效数据。

因此,最好删除第二个数组,让管理生命周期的包装器对象成为您使用的对象。而且您只CStruct在需要时才进入。如果这不可能,您只需要非常小心,以确保事情不会超出范围。


推荐阅读