Best practice to access an union fields in Golang from a dll

2 weeks ago 12
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

Read Entire Article