ARTICLE AD BOX
I have a dll that exposes a method that executes a callback passing a pointer to a struct that we can think like this:
struct Msg { uint32_t type; uint32_t length; union data{ uint8_t max_buffer[65536]; int number; other_type another_type; std::string message; ..... }; };in Go I receive the data like this:
syscall.NewCallback(messageCallback) func messageCallback(*Message msg){ }the question is, how should I define Message?
I have in mind different options:
zero size array type Message { uint32 type uint32 dataSize [0]byte data } func messageCallback(*Message msg){ if msg.type == 3 && msg.dataSize >= unsafe.SizeOf(otherType{}) { myType := (*other_type)unsafe.Pointer(&msg.data) } else if msg.type== 4 { str := windows.BytePtrToString((*byte)unsafe.Pointer(&msg.data)) } }the advantage is that I access only the data I need, and I don't need to do any pointer arithmetic considering the padding. The code is compatible if in future I need to access more fields and if the dll will increase the size of the union in a future version. My only problem, is that these zero size array I am not very sure how they behave and if it's safe to use them.
No declaration
type Message { uint32 type uint32 dataSize } func messageCallback(*Message msg){ ptr := unsafe.Add(unsafe.Pointer(&msg) + unsafe.SizeOf(Message{}) if msg.type == 3 && msg.dataSize >= unsafe.SizeOf(otherType{}) { myType := (*other_type)ptr } else if msg.type== 4 { str := windows.BytePtrToString((*byte)ptr) } }The advantage is that I access only the data that I need without using these weird zero size array, the disadvantage, is that it's not always trivial to compute the pointer at the end of the struct considering padding and 32/64 bit.
Use max known size type Message { uint32 type uint32 dataSize [65535]byte data } func messageCallback(*Message msg){ if msg.type == 3 && msg.dataSize >= unsafe.SizeOf(otherType{}) { myType := (*other_type)unsafe.Pointer(&msg.data[0]) } else if msg.type== 4 { str := windows.BytePtrToString((*byte)unsafe.Pointer(&msg.data[0])) } }The advantage is that the struct is always properly aligned, I can allocate a similar object in Go and compare it the struct that I receive, they should have both same size and alignments. The disadvantages is that I access more data that I really need and if in future the API changes the size of the union I risk to read unallocated memory or if they will increase I will not match anymore the size.
Use size 1 type Message { uint32 type uint32 dataSize [1]byte data }This is a pattern that I have seen very often in windows sdk with ANYSIZE_ARRAY Same advantages of zero size array, but it assumes to receive always at least 1 byte.
Use the max size I know I need (size of otherType) type Message { uint32 type uint32 dataSize [256]byte data } func messageCallback(*Message msg){ if msg.type == 3 && msg.dataSize >= unsafe.SizeOf(otherType{}) { myType := (*other_type)unsafe.Pointer(&msg.data[0]) } else if msg.type== 4 { str := windows.BytePtrToString((*byte)unsafe.Pointer(&msg.data[0])) } }I think has the same advantages of previous solution and should work if there will be no breaking changes in the api
