In C++ you can read in data from a FILE descriptor and simply reinterpret_cast it into a structure to interpret the data.
Is there an equivalent way to do this in Go?
As a very contrived example, consider the following where "ProcessBytes
" is simply a callback in which you are given an array of bytes that are continuously appended to when reading from a file.
struct PayloadHeader {
uint32_t TotalPayloadLength;
uint8_t PayloadType;
};
struct TextMessage {
PayloadHeader Header;
uint32_t SenderId;
uint32_t RecieverId;
char Text[64]; // null padded
};
void ProcessBytes(const uint8_t* data, size_t dataLength) {
if(dataLength < sizeof(PayloadHeader))
return;
const PayloadHeader* header = reinterpret_cast<const PayloadHeader*>(data);
if(header.PayloadType == TEXT_MESSAGE) {
if(header.TotalLength != sizeof(TextMessage))
return;
const TextMessage* text = reinterpret_cast<const TextMessage*>(data);
// Do something with the text message~
// Adjust the *data* to 'erase' the bytes after we are done processing it
// as a TextMessage
}
}
Right now answers are suggesting unsafe
, but they are not going over why you should not use unsafe
and what you should do instead. So let me give it a try.
In the C++ code you posted in OP, you seem to be writing a sort of binary format, which reads data through simple casting. Simple, but effective. The only obvious problem I can see is that it does not allow interoperability between Little Endian and Big Endian, but that's another matter.
The way you would approach binary encoding in Go is through the use of the handy package encoding/binary
, which is able to decode binary data directly into fixed-size structs (ie. without strings or slices, which are variable-length and so the length would need to be encoded arbitrarily).
Here is how I would implement your example in Go:
package main
import (
"bytes"
"encoding/binary"
"fmt"
)
const textMessage = 11
func main() {
// Set up our data - this is an example of the data I
// imagine you want to decode.
r := bytes.NewReader([]byte{
// Header
byte(textMessageLen + headerLen), 0, 0, 0,
11,
// Body
137, 0, 0, 0,
117, 0, 0, 0,
// Message content
'H', 'e', 'l', 'l', 'o', '!', 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
})
// We first read the header to decide what to do next.
// Notice that we explicitly pass an argument that indicates to
// parse integers using little endian, making this code portable.
var h Header
err := binary.Read(r, binary.LittleEndian, &h)
if err != nil {
fmt.Println(err)
return
}
switch h.Type {
case textMessage:
// It's a text message - make sure the length is right.
if textMessageLen != (int(h.Length) - headerLen) {
fmt.Println("Invalid payload length")
return
}
// Decode the data
var t TextMessage
err = binary.Read(r, binary.LittleEndian, &t)
if err != nil {
fmt.Println(err)
return
}
// Print it out
fmt.Printf("Sender: %d; Receiver: %d
Message: %s
",
t.Sender, t.Receiver, bytes.TrimRight(t.Text[:], "\x00"))
default:
fmt.Println("unknown payload type")
}
}
// If you need to find out what the encoded size of a struct is, don't use unsafe.Sizeof;
// use binary.Size instead.
var headerLen = binary.Size(Header{})
type Header struct {
Length uint32
Type uint8
}
var textMessageLen = binary.Size(TextMessage{})
type TextMessage struct {
Sender, Receiver uint32
Text [64]byte
}
So, here are a few things to note:
binary.Read
an io.Reader
, which means that when we read something from it the data read is discarded, so the pointer is automatically advanced.You can, but you should not because it is unsafe. Golang not define the memory model(as I known) of the struct
, it may change between golang versions. Do marshal/unmarshal is the regular way.
package main
import (
"fmt"
"unsafe"
)
type mys struct{
a int
}
func main() {
v := mys{0x1234abcd}
unsafeArrayP := (*[unsafe.Sizeof(v)]byte)(unsafe.Pointer(&v))
for i:=0;i<4;i++ {
fmt.Printf("%x,", unsafeArrayP[i])
}
v2 := 0x1234abcd
v3 := *((*mys)(unsafe.Pointer(&v2)))
fmt.Println(v == v3)
}
prints cd,ab,34,12,true