9

I'm posting this as a question/answer, as it took me a while to work out, and I wouldn't mind some feedback on my solution. In Go/CGo, how do you work with a C array passed as a pointer?

For example, with this C struct:

struct _GNetSnmpVarBind {                     
    guint32     *oid;       /* name of the variable */
    gsize       oid_len;    /* length of the name */
    ... and other fields
};  

I want to convert oid field to a Go string, how would I work with the guint32* pointer?

Vitaly Isaev
  • 5,392
  • 6
  • 45
  • 64
Sonia Hamilton
  • 4,229
  • 5
  • 35
  • 50

3 Answers3

8

You could convert the C array into a Go slice using a tip I saw in the go wiki

Untested but hopefully you get the idea! Don't let the slice live longer than the C data though as it points directly into it.

Last time I used this I had a look at the disassembly and it generates very efficient code.

func gIntArrayOidString(oid *_Ctype_guint32, oid_len _Ctype_gsize) (result string) {
    var oids []uint32
    sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&oids)))
    sliceHeader.Cap = oid_len
    sliceHeader.Len = oid_len
    sliceHeader.Data = uintptr(unsafe.Pointer(oid))

    var result string
    for _, value := range oids {
        result += fmt.Sprintf(".%d", value)
    }
    return result[1:]
}
szatmary
  • 29,969
  • 8
  • 44
  • 57
Nick Craig-Wood
  • 52,955
  • 12
  • 126
  • 132
4

The way I did it was to find the number of bytes to be read (size of a guint32 * oid_len), then did a binary.Read() on the number of bytes, then looped through be bytes in chunks of size. Easy in retrospect; the hard part was getting the type conversions working as Go is stricter than C.

For example here's the Go code for converting the guint32* to a Go string (representing an SNMP OID):

func gIntArrayOidString(oid *_Ctype_guint32, oid_len _Ctype_gsize) (result string) {
    size := int(unsafe.Sizeof(*oid))
    length := int(oid_len)
    gbytes := C.GoBytes(unsafe.Pointer(oid), (_Ctype_int)(size*length))
    buf := bytes.NewBuffer(gbytes)

    for i := 0; i < length; i++ {
        var out uint32
        if err := binary.Read(buf, binary.LittleEndian, &out); err == nil {
            result += fmt.Sprintf(".%d", out)
        } else {
            return "<error converting oid>"
        }
    }
    if len(result) > 1 {
        return result[1:] // strip leading dot
    }
    return "<error converting oid>"
}

Comments?


Context: the code is from gsnmpgo.

Sonia Hamilton
  • 4,229
  • 5
  • 35
  • 50
2

I assume the values from gsnmp are not necessarily in little-endian, but the native byte order. I would just use unsafe.Sizeof to iterate through the array. e.g.

package main

import (
    "unsafe"
    "fmt"
)

var ints = [...]int32 {1, 2, 3}

func main() {
    var result string
    var p *int32 = &ints[0]
    for i := 0; i < len(ints); i++ {
        result += fmt.Sprintf(".%d", *p)
        p = (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(p))+unsafe.Sizeof(*p)))
    }
    fmt.Println(result[1:])
}
Some Noob Student
  • 14,186
  • 13
  • 65
  • 103
axw
  • 6,908
  • 1
  • 24
  • 14