如何在Swift中读取结构字节

时间:2018-09-24 21:58:40

标签: swift struct unsafe-pointers

我正在使用Swift中的各种结构,我需要能够直接看一下它们的内存。

我怎么看一个字节的结构字节?

例如:

struct AwesomeStructure {
  var index: Int32
  var id: UInt16
  var stuff: UInt8
  // etc.
}

编译器不允许我这样做:

func scopeOfAwesomeStruct() {
    withUnsafePointer(to: &self, { (ptr: UnsafePointer<Int8>) in
    })
}

显然是因为withUnsafePointer是一个模板化函数,要求UnsafePointerself具有相同的类型。

那么,如何将self(我的结构)分解为8位呢?是的,我希望能够以4个,8位的片段等等依次查看index

(在这种情况下,我试图从C#移植CRC算法,但由于其他原因,我也对此问题感到困惑。)

3 个答案:

答案 0 :(得分:1)

这是一个不错的第一近似值。诀窍是使用Swift.withUnsafeBytes(_:)来获取UnsafeRawBufferPointer,然后可以使用Data轻松地将其转换为Data.init<SourceType>(buffer: UnsafeMutableBufferPointer<SourceType>)

这会导致内存的副本,因此您不必担心任何类型的悬空指针问题。

import Foundation

struct AwesomeStructure {
    let index: Int32 = 0x56
    let id: UInt16 = 0x34
    let stuff: UInt8 = 0x12
}

func toData<T>(_ input: inout T) -> Data {
    var data = withUnsafeBytes(of: &input, Data.init)
    let alignment = MemoryLayout<T>.alignment
    let remainder = data.count % alignment

    if remainder == 0 {
        return data
    }
    else {
        let paddingByteCount = alignment - remainder
        return data + Data(count: paddingByteCount)
    }
}

extension Data {
    var prettyString: String {
        return self.enumerated()
            .lazy
            .map { byteNumber, byte in String(format:"/* %02i */ 0x%02X", byteNumber, byte) }
            .joined(separator: "\n")
    }
}

var x = AwesomeStructure()
let d = toData(&x)
print(d.prettyString)

答案 1 :(得分:1)

您可以像这样直接使用withUnsafeBytes(_:)

mutating func scopeOfAwesomeStruct() {
    withUnsafeBytes(of: &self) {rbp in
        let ptr = rbp.baseAddress!.assumingMemoryBound(to: UInt8.self)
        //...
    }
}

如上所述,请勿将ptr导出到闭包之外。

即使您具有知道结构长度的函数,也不安全。 Swift API的稳定性尚未声明。不保证结构的任何布局细节,包括属性的顺序以及它们如何放置填充。可能与C#结构不同,并且可能会产生与C#不同的结果。

我(和许多其他开发人员)相信并期望current layout strategy在不久的将来不会改变,所以我会写一些像您一样的代码。但我认为这并不安全。记住Swift不是C。

(不过,如果将结构的内容复制到Data中,都是一样的。)

如果要使用C进行严格的布局,可以编写C结构并将其导入到Swift项目中。

答案 2 :(得分:1)

struct AwesomeStructure {
    let index: Int32
    let id: UInt16
    let stuff: UInt8
}

extension AwesomeStructure {
    init(data: Data) {
        self.index = data[0...3].withUnsafeBytes { $0.pointee }
        self.id    = data[4...5].withUnsafeBytes { $0.pointee }
        self.stuff = data[6...6].withUnsafeBytes { $0.pointee }
    }
    var data: Data {
        return index.data + id.data + stuff.data
    }
}

extension Numeric {
    var data: Data {
        var source = self
        return Data(bytes: &source, count: MemoryLayout<Self>.size)
    }
}

let awesomeStructure = AwesomeStructure(index: 1, id: 2, stuff: 3)
let data = awesomeStructure.data
print(data)  //  7 bytes

let structFromData = AwesomeStructure(data: data)
print(structFromData)   // "AwesomeStructure(index: 1, id: 2, stuff: 3)\n"

如果您希望避免手动输入范围,则可以使用另一种方法,可以创建一个通用方法来从数据的特定位置返回对象,如下所示:

extension Data {
    func object<T>(at index: Index) -> T {
        return self[index..<index+MemoryLayout<T>.size].withUnsafeBytes { $0.pointee }
    }
}

并像这样使用它:

extension AwesomeStructure {
    init(data: Data) {
        var idx = data.startIndex
        self.index = data.object(at: idx)
        idx += MemoryLayout.size(ofValue: index)
        self.id = data.object(at: idx)
        idx += MemoryLayout.size(ofValue: id)
        self.stuff = data.object(at: idx)
    }
}