Go/CGo – 如何使用作为指针传递的C数组

huangapple go评论85阅读模式
英文:

Go/CGo - how do you use a C array passed as a pointer

问题

我将这个问题/答案发布出来,因为我花了一些时间来解决它,我不介意对我的解决方案提供一些反馈。在Go/CGo中,如何处理作为指针传递的C数组?

例如,对于这个C结构体:

struct _GNetSnmpVarBind {                     
    guint32     *oid;       /* 变量的名称 */
    gsize       oid_len;    /* 名称的长度 */
    ... 和其他字段
};  

我想将oid字段转换为Go字符串,我应该如何处理guint32*指针?

英文:

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?

答案1

得分: 8

你可以使用我在Go Wiki上看到的提示将C数组转换为Go切片。

未经测试,但希望你能理解!不要让切片的生命周期超过C数据,因为它直接指向它。

上次我使用这个方法时,我查看了反汇编代码,它生成的代码非常高效。

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:]
}
英文:

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:]
}

答案2

得分: 4

我是你的中文翻译,以下是翻译好的部分:

我做的方法是先找到要读取的字节数(guint32 * oid_len的大小),然后对这些字节进行二进制读取(binary.Read()),然后按照指定大小循环遍历字节。回想起来很简单;困难的部分是让类型转换正常工作,因为Go语言比C语言更严格。

例如,下面是将guint32*转换为Go字符串(表示SNMP OID)的Go代码:

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:] // 去掉开头的点
    }
    return "<error converting oid>"
}

有什么意见吗?

英文:

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 &lt; length; i++ {
        var out uint32
        if err := binary.Read(buf, binary.LittleEndian, &amp;out); err == nil {
            result += fmt.Sprintf(&quot;.%d&quot;, out)
        } else {
            return &quot;&lt;error converting oid&gt;&quot;
        }
    }
    if len(result) &gt; 1 {
        return result[1:] // strip leading dot
    }
    return &quot;&lt;error converting oid&gt;&quot;
}

Comments?


Context: the code is from gsnmpgo.

答案3

得分: 2

我假设gsnmp中的值不一定是小端字节顺序,而是本机字节顺序。我只需使用unsafe.Sizeof来遍历数组。例如。

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:])
}
英文:

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.

<!-- language: go -->

package main

import (
    &quot;unsafe&quot;
    &quot;fmt&quot;
)

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

func main() {
    var result string
    var p *int32 = &amp;ints[0]
    for i := 0; i &lt; len(ints); i++ {
        result += fmt.Sprintf(&quot;.%d&quot;, *p)
        p = (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(p))+unsafe.Sizeof(*p)))
    }
    fmt.Println(result[1:])
}

huangapple
  • 本文由 发表于 2013年2月12日 13:53:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/14826319.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定