How to read bytes of struct in Swift

2.6k Views Asked by At

I am working with a variety of structs in Swift that I need to be able to look at the memory of directly.

How can I look at a struct byte for byte?

For example:

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

The compiler will not allow me to do this:

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

Obviously because withUnsafePointer is a templated function that requires the UnsafePointer to be the same type as self.

So, how can I break down self (my structs) into 8 bit pieces? Yes, I want to be able to look at index in 4, 8-bit pieces, and so-on.

(In this case, I'm trying to port a CRC algorithm from C#, but I have been confounded by this problem for other reasons as well.)

3

There are 3 best solutions below

2
On BEST ANSWER

You can use withUnsafeBytes(_:) directly like this:

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

As already noted, do not export ptr outside of the closure.

And it is not safe even if you have a function that knows the length of the structure. Swift API stability is not declared yet. Any of the layout details of structs are not guaranteed, including the orders of the properties and how they put paddings. Which may be different than the C# structs and may generate the different result than that of C#.

I (and many other developers) believe and expect that the current layout strategy would not change in the near future, so I would write some code like yours. But I do not think it's safe. Remember Swift is not C.

(Though, it's all the same if you copy the contents of a struct into a Data.)

If you want a strictly exact layout with C, you can write a C struct and import it into your Swift project.

0
On

edit/update: Xcode 12.5 • Swift 5.4


extension ContiguousBytes {
    func object<T>() -> T { withUnsafeBytes { $0.load(as: T.self) } }
}

extension Data {
    func subdata<R: RangeExpression>(in range: R) -> Self where R.Bound == Index {
        subdata(in: range.relative(to: self) )
    }
    func object<T>(at offset: Int) -> T { subdata(in: offset...).object() }
}

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

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

extension AwesomeStructure {
    init(data: Data) {
        index = data.object()
        id = data.object(at: 4)
        stuff = data.object(at: 6)
    }
    var data: Data { index.data + id.data + stuff.data }
}

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"
8
On

Here's a decent first approximation. The trick is to use Swift.withUnsafeBytes(_:) to get a UnsafeRawBufferPointer, which can then be easily converted to Data using Data.init<SourceType>(buffer: UnsafeMutableBufferPointer<SourceType>).

This causes a copy of the memory, so you don't have to worry about any sort of dangling pointer issues.

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)