代码之家  ›  专栏  ›  技术社区  ›  Brandon M

如何在Swift中读取结构的字节

  •  0
  • Brandon M  · 技术社区  · 6 年前

    我正在用Swift处理各种各样的结构,我需要能够直接查看它的内存。

    例如:

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

    编译器不允许我这样做:

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

    显然是因为 withUnsafePointer 是一个模板化函数,需要 UnsafePointer 与…类型相同 self .

    那么,我怎么能崩溃呢 自己 (我的结构)分成8位的片段?是的,我想看看 index 4位、8位等。

    (在本例中,我尝试从C#移植一个CRC算法,但由于其他原因,我也被这个问题弄糊涂了。)

    3 回复  |  直到 6 年前
        1
  •  2
  •   OOPer    6 年前

    你可以用 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
  •  2
  •   Leo Dabus    6 年前
    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)
        }
    }
    
        3
  •  1
  •   Alexander    6 年前

    这是一个不错的第一近似值。诀窍是使用 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)